pax_global_header00006660000000000000000000000064136217744130014522gustar00rootroot0000000000000052 comment=04f16942ab22f42b920bcd459c707b3d48a04859 BambooTracker-0.3.5/000077500000000000000000000000001362177441300142425ustar00rootroot00000000000000BambooTracker-0.3.5/.appveyor.yml000066400000000000000000000047061362177441300167170ustar00rootroot00000000000000#---------------------------------# # general configuration # #---------------------------------# # version format version: 0.3.5.{build} # branches to build branches: # whitelist only: - master # Do not build on tags skip_tags: true # Skipping commits with particular message or from specific user skip_commits: message: /Created.*\.(png|jpg|jpeg|bmp|gif|md)/ # Regex for matching commit message files: - '*.md' - '*.txt' - 'LICENSE' - '.gitignore' #---------------------------------# # environment configuration # #---------------------------------# # set clone depth clone_depth: 3 # clone entire repository history if not defined environment: matrix: # Windows 7 or later - APPVEYOR_JOB_NAME: for Windows 7 or later APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 COMPILER: C:\Qt\Tools\mingw530_32\bin QT: C:\Qt\5.10.1\mingw53_32\bin DEST: BambooTracker-v%APPVEYOR_BUILD_VERSION%-dev.zip # Windows XP - APPVEYOR_JOB_NAME: for Windows XP APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 COMPILER: C:\Qt\Tools\mingw492_32\bin QT: C:\Qt\5.5\mingw492_32\bin DEST: BambooTracker-v%APPVEYOR_BUILD_VERSION%-dev-winXP.zip # scripts that run after cloning repository install: - git submodule init - git submodule update #---------------------------------# # build configuration # #---------------------------------# # build platform, i.e. x86, x64, Any CPU. This setting is optional. platform: x86 # scripts to run before build before_build: - set PATH=%QT%;%COMPILER%;%PATH% - cd BambooTracker # to run your custom scripts instead of automatic MSBuild build_script: - qmake BambooTracker.pro CONFIG+=release CONFIG-=debug - mingw32-make - md bin - copy release\BambooTracker.exe bin - cd bin - windeployqt BambooTracker.exe - ren translations lang - cd .. - copy .qm\*.qm bin\lang # scripts to run after build (working directory and environment changes are persisted from the previous steps) after_build: - cd %APPVEYOR_BUILD_FOLDER% - set DEPLOY_FILES=%APPVEYOR_BUILD_FOLDER%\BambooTracker\bin\* - set DEPLOY_FILES=%DEPLOY_FILES% licenses img demos specs skins - set DEPLOY_FILES=%DEPLOY_FILES% *.md LICENSE - 7z a -tzip %DEST% %DEPLOY_FILES% #---------------------------------# # artifacts configuration # #---------------------------------# artifacts: # pushing a single file - path: $(DEST) BambooTracker-0.3.5/.gitattributes000066400000000000000000000047261362177441300171460ustar00rootroot00000000000000############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain BambooTracker-0.3.5/.gitignore000066400000000000000000000055521362177441300162410ustar00rootroot00000000000000# Created by https://www.gitignore.io/api/qt,c++,linux,macos,emacs,windows,visualstudiocode # Edit at https://www.gitignore.io/?templates=qt,c++,linux,macos,emacs,windows,visualstudiocode ### C++ ### # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ### Emacs ### # -*- mode: gitignore; -*- *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile # directory configuration .dir-locals.el # network security /network-security.data ### Linux ### # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Qt ### # C++ objects and libs # Qt-es object_script.*.Release object_script.*.Debug *_plugin_import.cpp /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp moc_*.h qrc_*.cpp ui_*.h *.qmlc *.jsc Makefile* *build-* # Qt unit tests target_wrapper.* # QtCreator *.autosave # QtCreator Qml *.qmlproject.user *.qmlproject.user.* # QtCreator CMake CMakeLists.txt.user* # QtCreator 4.8< compilation database compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ### VisualStudioCode Patch ### # Ignore all local history of files .history ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/qt,c++,linux,macos,emacs,windows,visualstudiocode ########## !/BambooTracker/res/icon BambooTracker-0.3.5/.travis.yml000066400000000000000000000034761362177441300163650ustar00rootroot00000000000000language: cpp matrix: include: - os: linux dist: xenial - os: osx osx_image: xcode9.3 addons: apt: sources: - sourceline: 'ppa:ubuntu-sdk-team/ppa' packages: - qt5-default - qttools5-dev-tools - qtmultimedia5-dev - libqt5multimedia5-plugins - libasound2-dev - libjack-dev - libpulse-dev homebrew: packages: - qt update: true git: depth: 3 branches: only: - master - "/^v[0-9\\.]+$/" before_install: # osx: force linking of qt into PATH - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew link --force qt; fi script: - cd BambooTracker - qmake - make before_deploy: - macdeployqt BambooTracker.app -verbose=2 - mv .qm lang - mv lang BambooTracker.app/Contents/Resources - mv BambooTracker.app .. - cd .. - zip -r BambooTracker-$TRAVIS_TAG-macOS.zip BambooTracker.app *.md img demos licenses specs skins LICENSE deploy: provider: releases api_key: secure: cZtUOLRT6zHXsmcyZvhQzZVEjG4vJ0j3FYYu+/HAgRdZ8Rf+8hpNuELMcabMlxd4/7AOkjGDo6gqIOt0VlwtqgWtzlJwvUbMXIgNyw2bjIETUsh6rrGBtZNm9r5my7Iv4xtFw9SBCOdyykKo/kVUpomdPwPm1VwhCjgKW9eNzKW49tcmG2BHZiL2ZBibxAqGJmSEG0RkYa6Ycxbx4qFSJ6hb40MiCrtAyyyFnzdpHEdBsgpbXO/wBeQDRYaf88lC+tuh2uz81m8wNkLBFU6fIA7XgKcChy2C1tMdsMhSILSx6Zgthfb3l/EsGcbwx8dj+uWJybPc3kWHly17bdS3hjNG8N/IpNHSetmk6tPXNxiGVFxFiCjEPJohMuSksx5MFrdJgUkOUhT9Vr2ZPXOoXTjhnXAasJAgGklMlcxni+H2hCSAzIokFMOGwxRObCmXjsD9U2TvujAblYf2VU2cimoxkERlZdAwt9QZkWJ6xSW2sD8DaxQ9hcJcovXIEC9QEfqSA0klkz1GQHgJ/lMvyZPYJPzq7KkbQiwIk3yYtvlpigoWgvOslCh9hgTgXVqvHF2rcOQTgYt1q6IRIu4NwSqqhBj2BcJdM+wfNsjoiJ2+0+O6Tdc9SPSnfi9b4+i6k32FeCcNICuE+eXU2GYCosviBAC6KpfSH6Fw467ygds= file: $TRAVIS_BUILD_DIR/BambooTracker-$TRAVIS_TAG-macOS.zip skip_cleanup: true on: repo: rerrahkr/BambooTracker tags: true condition: $TRAVIS_OS_NAME = osx BambooTracker-0.3.5/BambooTracker.1000066400000000000000000000010601362177441300170340ustar00rootroot00000000000000.TH BambooTracker 1 "December 15 2018" .SH NAME BambooTracker \- YM2608 (OPNA) tracker .SH SYNOPSIS .B BambooTracker .RI " files" ... .br .SH DESCRIPTION This manual page quickly summarizes the .B BambooTracker command. .PP \fBBambooTracker\fP is a tracker for YM2608 (OPNA) which was used in NEC PC-8801/9801 series computers. .SH OPTIONS No options are supported. .SH SEE ALSO .BR cheesecutter (1), .BR protracker (1), .BR hivelytracker (1), .BR qtractor (1), .BR traverso (1), .BR goattracker (1). .br .SH INTERNET https://github.com/rerrahkr/BambooTracker BambooTracker-0.3.5/BambooTracker.desktop000066400000000000000000000013421362177441300203500ustar00rootroot00000000000000[Desktop Entry] Name=BambooTracker GenericName=OPNA Tracker GenericName[jp]=OPNAのトラッカーです GenericName[fr]=Tracker OPNA Comment=A tracker for the YM2608 (OPNA), which was used in NEC PC-8801/9801 series of computers Comment[jp]=このアプリケーションはNEC PC-8801/9801シリーズに搭載されていたFM音源YM2608(OPNA)向けのトラッカーです。 Comment[de]=Ein Tracker für den YM2608 (OPNA), welcher in NECs PC-8801/9801 Serien an Computern verwendet wurde Comment[fr]=Un tracker pour le YM2608 (OPNA), qui fut utilisé dans la série d'ordinateurs NEC PC-8801/9801 Exec=BambooTracker Icon=BambooTracker Terminal=false Type=Application Categories=AudioVideo;AudioVideoEditing; Keywords=tracker;music; BambooTracker-0.3.5/BambooTracker.fr.1000066400000000000000000000014011362177441300174410ustar00rootroot00000000000000.TH BambooTracker 1 "15 décembre 2018" .SH NOM BambooTracker \- Tracker YM2608 (OPNA) .SH SYNOPSIS .B BambooTracker .RI " fichiers" ... .br .SH DESCRIPTION Cette page de manuel documente rapidement la commande .B BambooTracker .PP \fBBambooTracker\fP est un tracker pour le YM2608 (OPNA), qui fut utilisé dans la série d'ordinateurs NEC PC-8801/9801. .SH OPTIONS Aucune option n'est supportée. .SH VOIR ÉGALEMENT .BR cheesecutter (1), .BR protracker (1), .BR hivelytracker (1), .BR qtractor (1), .BR traverso (1), .BR goattracker (1). .br .SH INTERNET https://github.com/rerrahkr/BambooTracker .PP Cette page de manuel en français a été traduite par Olivier HUMBERT .B , pour le projet LibraZiK (mais peut être utilisée par d'autres). BambooTracker-0.3.5/BambooTracker/000077500000000000000000000000001362177441300167555ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/BambooTracker.pro000066400000000000000000000457241362177441300222260ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2018-06-09T16:20:11 # #------------------------------------------------- QT += core gui multimedia greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = BambooTracker TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 # This produces the installation rule for the program and resources. # Use a default destination prefix if none is given. isEmpty(PREFIX) { win32:PREFIX = C:/BambooTracker else:PREFIX = /usr/local } INSTALLS += target win32 { QM_FILES_INSTALL_PATH = $$PREFIX/lang target.path = $$PREFIX } else { QM_FILES_INSTALL_PATH = $$PREFIX/share/BambooTracker/lang target.path = $$PREFIX/bin } CONFIG += c++14 SOURCES += \ chips/c86ctl/c86ctl_wrapper.cpp \ gui/color_palette_handler.cpp \ gui/effect_description.cpp \ gui/effect_list_dialog.cpp \ gui/instrument_editor/arpeggio_macro_editor.cpp \ gui/instrument_editor/tone_noise_macro_editor.cpp \ gui/keyboard_shortcut_list_dialog.cpp \ main.cpp \ gui/mainwindow.cpp \ chips/chip.cpp \ chips/opna.cpp \ chips/resampler.cpp \ chips/mame/2608intf.c \ chips/mame/emu2149.c \ chips/mame/fm.c \ chips/mame/ymdeltat.c \ chips/nuked/nuke2608intf.c \ chips/nuked/ym3438.c \ bamboo_tracker.cpp \ module/effect.cpp \ playback.cpp \ stream/audio_stream.cpp \ jam_manager.cpp \ pitch_converter.cpp \ instrument/instruments_manager.cpp \ command/command_manager.cpp \ command/instrument/add_instrument_command.cpp \ command/instrument/remove_instrument_command.cpp \ gui/command/instrument/add_instrument_qt_command.cpp \ gui/command/instrument/remove_instrument_qt_command.cpp \ gui/instrument_editor/instrument_editor_fm_form.cpp \ gui/instrument_editor/fm_operator_table.cpp \ gui/labeled_vertical_slider.cpp \ gui/labeled_horizontal_slider.cpp \ gui/slider_style.cpp \ gui/command/instrument/change_instrument_name_qt_command.cpp \ command/instrument/change_instrument_name_command.cpp \ opna_controller.cpp \ instrument/instrument.cpp \ instrument/envelope_fm.cpp \ gui/event_guard.cpp \ stream/audio_stream_rtaudio.cpp \ tick_counter.cpp \ module/module.cpp \ module/song.cpp \ module/pattern.cpp \ module/track.cpp \ module/step.cpp \ gui/order_list_editor/order_list_panel.cpp \ gui/order_list_editor/order_list_editor.cpp \ gui/pattern_editor/pattern_editor_panel.cpp \ gui/pattern_editor/pattern_editor.cpp \ gui/instrument_editor/instrument_editor_ssg_form.cpp \ gui/command/pattern/set_key_off_to_step_qt_command.cpp \ command/pattern/set_key_off_to_step_command.cpp \ command/pattern/set_key_on_to_step_command.cpp \ gui/command/pattern/set_key_on_to_step_qt_command.cpp \ gui/command/pattern/set_instrument_to_step_qt_command.cpp \ command/pattern/set_instrument_to_step_command.cpp \ gui/command/pattern/erase_instrument_in_step_qt_command.cpp \ command/pattern/erase_instrument_in_step_command.cpp \ gui/command/pattern/set_volume_to_step_qt_command.cpp \ command/pattern/set_volume_to_step_command.cpp \ gui/command/pattern/erase_volume_in_step_qt_command.cpp \ command/pattern/erase_volume_in_step_command.cpp \ command/pattern/set_effect_id_to_step_command.cpp \ gui/command/pattern/set_effect_id_to_step_qt_command.cpp \ command/pattern/erase_effect_in_step_command.cpp \ gui/command/pattern/erase_effect_in_step_qt_command.cpp \ command/pattern/set_effect_value_to_step_command.cpp \ gui/command/pattern/set_effect_value_to_step_qt_command.cpp \ command/pattern/erase_effect_value_in_step_command.cpp \ gui/command/pattern/erase_effect_value_in_step_qt_command.cpp \ command/pattern/insert_step_command.cpp \ command/pattern/delete_previous_step_command.cpp \ gui/command/pattern/insert_step_qt_command.cpp \ gui/command/pattern/delete_previous_step_qt_command.cpp \ gui/command/pattern/erase_step_qt_command.cpp \ command/pattern/erase_step_command.cpp \ gui/command/instrument/deep_clone_instrument_qt_command.cpp \ command/instrument/deep_clone_instrument_command.cpp \ command/instrument/clone_instrument_command.cpp \ gui/command/instrument/clone_instrument_qt_command.cpp \ command/order/set_pattern_to_order_command.cpp \ gui/command/order/set_pattern_to_order_qt_command.cpp \ command/order/insert_order_below_command.cpp \ command/order/delete_order_command.cpp \ gui/command/order/insert_order_below_qt_command.cpp \ gui/command/order/delete_order_qt_command.cpp \ command/pattern/paste_copied_data_to_pattern_command.cpp \ gui/command/pattern/paste_copied_data_to_pattern_qt_command.cpp \ command/pattern/erase_cells_in_pattern_command.cpp \ gui/command/pattern/erase_cells_in_pattern_qt_command.cpp \ command/order/paste_copied_data_to_order_command.cpp \ gui/command/order/paste_copied_data_to_order_qt_command.cpp \ gui/instrument_editor/instrument_form_manager.cpp \ instrument/lfo_fm.cpp \ gui/instrument_editor/visualized_instrument_macro_editor.cpp \ instrument/command_sequence.cpp \ instrument/effect_iterator.cpp \ command/pattern/paste_mix_copied_data_to_pattern_command.cpp \ gui/command/pattern/paste_mix_copied_data_to_pattern_qt_command.cpp \ gui/command/pattern/decrease_note_key_in_pattern_qt_command.cpp \ gui/command/pattern/increase_note_key_in_pattern_qt_command.cpp \ gui/command/pattern/increase_note_octave_in_pattern_qt_command.cpp \ gui/command/pattern/decrease_note_octave_in_pattern_qt_command.cpp \ command/pattern/increase_note_key_in_pattern_command.cpp \ command/pattern/decrease_note_key_in_pattern_command.cpp \ command/pattern/increase_note_octave_in_pattern_command.cpp \ command/pattern/decrease_note_octave_in_pattern_command.cpp \ gui/module_properties_dialog.cpp \ module/groove.cpp \ gui/groove_settings_dialog.cpp \ gui/configuration_dialog.cpp \ command/pattern/expand_pattern_command.cpp \ gui/command/pattern/expand_pattern_qt_command.cpp \ command/pattern/shrink_pattern_command.cpp \ gui/command/pattern/shrink_pattern_qt_command.cpp \ instrument/abstract_instrument_property.cpp \ command/order/duplicate_order_command.cpp \ command/order/move_order_command.cpp \ command/order/clone_patterns_command.cpp \ command/order/clone_order_command.cpp \ gui/command/order/duplicate_order_qt_command.cpp \ gui/command/order/move_order_qt_command.cpp \ gui/command/order/clone_patterns_qt_command.cpp \ gui/command/order/clone_order_qt_command.cpp \ gui/command/pattern/set_echo_buffer_access_qt_command.cpp \ command/pattern/set_echo_buffer_access_command.cpp \ gui/comment_edit_dialog.cpp \ io/file_io.cpp \ io/binary_container.cpp \ gui/command/pattern/interpolate_pattern_qt_command.cpp \ command/pattern/interpolate_pattern_command.cpp \ gui/command/pattern/reverse_pattern_qt_command.cpp \ command/pattern/reverse_pattern_command.cpp \ gui/command/pattern/replace_instrument_in_pattern_qt_command.cpp \ command/pattern/replace_instrument_in_pattern_command.cpp \ chips/export_container.cpp \ gui/vgm_export_settings_dialog.cpp \ gui/wave_export_settings_dialog.cpp \ configuration.cpp \ gui/configuration_handler.cpp \ gui/color_palette.cpp \ gui/command/pattern/paste_overwrite_copied_data_to_pattern_qt_command.cpp \ command/pattern/paste_overwrite_copied_data_to_pattern_command.cpp \ io/file_io_error.cpp \ format/wopn_file.c \ instrument/bank.cpp \ gui/instrument_selection_dialog.cpp \ gui/s98_export_settings_dialog.cpp \ stream/timer.cpp \ io/module_io.cpp \ io/export_handler.cpp \ io/instrument_io.cpp \ io/bank_io.cpp \ gui/fm_envelope_set_edit_dialog.cpp \ gui/file_history_handler.cpp \ gui/file_history.cpp \ midi/midi.cpp \ gui/q_application_wrapper.cpp \ gui/wave_visual.cpp HEADERS += \ chips/c86ctl/c86ctl.h \ chips/c86ctl/c86ctl_wrapper.hpp \ command/command_id.hpp \ enum_hash.hpp \ gui/color_palette_handler.hpp \ gui/effect_description.hpp \ gui/effect_list_dialog.hpp \ gui/instrument_editor/arpeggio_macro_editor.hpp \ gui/instrument_editor/tone_noise_macro_editor.hpp \ gui/jam_layout.hpp \ gui/keyboard_shortcut_list_dialog.hpp \ gui/mainwindow.hpp \ chips/mame/2608intf.h \ chips/mame/emu2149.h \ chips/mame/emutypes.h \ chips/mame/fm.h \ chips/mame/mamedef.h \ chips/mame/ymdeltat.h \ chips/nuked/nuke2608intf.h \ chips/nuked/ym3438.h \ chips/chip.hpp \ chips/chip_misc.h \ chips/opna.hpp \ chips/resampler.hpp \ bamboo_tracker.hpp \ module/effect.hpp \ playback.hpp \ stream/audio_stream.hpp \ chips/chip_def.h \ jam_manager.hpp \ misc.hpp \ pitch_converter.hpp \ instrument/instruments_manager.hpp \ command/command_manager.hpp \ command/instrument/add_instrument_command.hpp \ command/instrument/remove_instrument_command.hpp \ command/commands.hpp \ gui/command/instrument/add_instrument_qt_command.hpp \ gui/command/commands_qt.hpp \ gui/command/instrument/remove_instrument_qt_command.hpp \ gui/instrument_editor/instrument_editor_fm_form.hpp \ gui/instrument_editor/fm_operator_table.hpp \ gui/labeled_vertical_slider.hpp \ gui/labeled_horizontal_slider.hpp \ gui/slider_style.hpp \ gui/command/instrument/change_instrument_name_qt_command.hpp \ command/instrument/change_instrument_name_command.hpp \ opna_controller.hpp \ instrument/instrument.hpp \ instrument/envelope_fm.hpp \ gui/event_guard.hpp \ stream/audio_stream_rtaudio.hpp \ tick_counter.hpp \ module/module.hpp \ module/song.hpp \ module/pattern.hpp \ module/track.hpp \ module/step.hpp \ gui/order_list_editor/order_list_panel.hpp \ gui/order_list_editor/order_list_editor.hpp \ gui/pattern_editor/pattern_editor_panel.hpp \ gui/pattern_editor/pattern_editor.hpp \ gui/instrument_editor/instrument_editor_ssg_form.hpp \ gui/command/pattern/set_key_off_to_step_qt_command.hpp \ command/pattern/set_key_off_to_step_command.hpp \ gui/command/pattern/pattern_commands_qt.hpp \ command/pattern/set_key_on_to_step_command.hpp \ gui/command/pattern/set_key_on_to_step_qt_command.hpp \ gui/pattern_editor/pattern_position.hpp \ gui/command/pattern/set_instrument_to_step_qt_command.hpp \ command/pattern/set_instrument_to_step_command.hpp \ gui/command/pattern/erase_instrument_in_step_qt_command.hpp \ command/pattern/erase_instrument_in_step_command.hpp \ gui/command/pattern/set_volume_to_step_qt_command.hpp \ command/pattern/set_volume_to_step_command.hpp \ gui/command/pattern/erase_volume_in_step_qt_command.hpp \ command/pattern/erase_volume_in_step_command.hpp \ command/pattern/set_effect_id_to_step_command.hpp \ gui/command/pattern/set_effect_id_to_step_qt_command.hpp \ command/pattern/erase_effect_in_step_command.hpp \ gui/command/pattern/erase_effect_in_step_qt_command.hpp \ command/pattern/set_effect_value_to_step_command.hpp \ gui/command/pattern/set_effect_value_to_step_qt_command.hpp \ command/pattern/erase_effect_value_in_step_command.hpp \ gui/command/pattern/erase_effect_value_in_step_qt_command.hpp \ command/pattern/insert_step_command.hpp \ command/pattern/delete_previous_step_command.hpp \ gui/command/pattern/insert_step_qt_command.hpp \ gui/command/pattern/delete_previous_step_qt_command.hpp \ gui/command/pattern/erase_step_qt_command.hpp \ command/pattern/erase_step_command.hpp \ gui/command/instrument/deep_clone_instrument_qt_command.hpp \ command/instrument/deep_clone_instrument_command.hpp \ command/instrument/clone_instrument_command.hpp \ gui/command/instrument/clone_instrument_qt_command.hpp \ gui/order_list_editor/order_position.hpp \ command/order/set_pattern_to_order_command.hpp \ gui/command/order/set_pattern_to_order_qt_command.hpp \ gui/command/order/order_commands.hpp \ command/order/insert_order_below_command.hpp \ command/order/delete_order_command.hpp \ gui/command/order/insert_order_below_qt_command.hpp \ gui/command/order/delete_order_qt_command.hpp \ command/pattern/paste_copied_data_to_pattern_command.hpp \ gui/command/pattern/paste_copied_data_to_pattern_qt_command.hpp \ command/pattern/erase_cells_in_pattern_command.hpp \ gui/command/pattern/erase_cells_in_pattern_qt_command.hpp \ command/order/paste_copied_data_to_order_command.hpp \ gui/command/order/paste_copied_data_to_order_qt_command.hpp \ gui/instrument_editor/instrument_form_manager.hpp \ instrument/lfo_fm.hpp \ gui/instrument_editor/visualized_instrument_macro_editor.hpp \ instrument/command_sequence.hpp \ instrument/sequence_iterator_interface.hpp \ instrument/effect_iterator.hpp \ command/pattern/paste_mix_copied_data_to_pattern_command.hpp \ gui/command/pattern/paste_mix_copied_data_to_pattern_qt_command.hpp \ gui/command/pattern/decrease_note_key_in_pattern_qt_command.hpp \ gui/command/pattern/increase_note_key_in_pattern_qt_command.hpp \ gui/command/pattern/increase_note_octave_in_pattern_qt_command.hpp \ gui/command/pattern/decrease_note_octave_in_pattern_qt_command.hpp \ command/pattern/increase_note_key_in_pattern_command.hpp \ command/pattern/decrease_note_key_in_pattern_command.hpp \ command/pattern/increase_note_octave_in_pattern_command.hpp \ command/pattern/decrease_note_octave_in_pattern_command.hpp \ gui/module_properties_dialog.hpp \ module/groove.hpp \ gui/groove_settings_dialog.hpp \ gui/configuration_dialog.hpp \ command/pattern/expand_pattern_command.hpp \ gui/command/pattern/expand_pattern_qt_command.hpp \ command/pattern/shrink_pattern_command.hpp \ gui/command/pattern/shrink_pattern_qt_command.hpp \ command/abstract_command.hpp \ instrument/abstract_instrument_property.hpp \ command/order/duplicate_order_command.hpp \ command/order/move_order_command.hpp \ command/order/clone_patterns_command.hpp \ command/order/clone_order_command.hpp \ gui/command/order/duplicate_order_qt_command.hpp \ gui/command/order/move_order_qt_command.hpp \ gui/command/order/clone_patterns_qt_command.hpp \ gui/command/order/clone_order_qt_command.hpp \ gui/command/pattern/set_echo_buffer_access_qt_command.hpp \ command/pattern/set_echo_buffer_access_command.hpp \ gui/comment_edit_dialog.hpp \ io/file_io.hpp \ io/binary_container.hpp \ version.hpp \ gui/command/pattern/interpolate_pattern_qt_command.hpp \ command/pattern/interpolate_pattern_command.hpp \ gui/command/pattern/reverse_pattern_qt_command.hpp \ command/pattern/reverse_pattern_command.hpp \ gui/command/pattern/replace_instrument_in_pattern_qt_command.hpp \ command/pattern/replace_instrument_in_pattern_command.hpp \ chips/export_container.hpp \ gui/vgm_export_settings_dialog.hpp \ gui/wave_export_settings_dialog.hpp \ io/gd3_tag.hpp \ configuration.hpp \ gui/configuration_handler.hpp \ gui/color_palette.hpp \ gui/command/pattern/paste_overwrite_copied_data_to_pattern_qt_command.hpp \ command/pattern/paste_overwrite_copied_data_to_pattern_command.hpp \ io/file_io_error.hpp \ format/wopn_file.h \ instrument/bank.hpp \ gui/instrument_selection_dialog.hpp \ io/s98_tag.hpp \ gui/s98_export_settings_dialog.hpp \ chips/scci/scci.h \ chips/scci/SCCIDefines.h \ stream/timer.hpp \ io/module_io.hpp \ io/io_handlers.hpp \ io/export_handler.hpp \ io/instrument_io.hpp \ io/bank_io.hpp \ gui/fm_envelope_set_edit_dialog.hpp \ gui/file_history_handler.hpp \ gui/file_history.hpp \ midi/midi.hpp \ midi/midi_def.h \ gui/q_application_wrapper.hpp \ gui/wave_visual.hpp FORMS += \ gui/effect_list_dialog.ui \ gui/keyboard_shortcut_list_dialog.ui \ gui/mainwindow.ui \ gui/instrument_editor/instrument_editor_fm_form.ui \ gui/instrument_editor/fm_operator_table.ui \ gui/labeled_vertical_slider.ui \ gui/labeled_horizontal_slider.ui \ gui/order_list_editor/order_list_editor.ui \ gui/pattern_editor/pattern_editor.ui \ gui/instrument_editor/instrument_editor_ssg_form.ui \ gui/instrument_editor/visualized_instrument_macro_editor.ui \ gui/module_properties_dialog.ui \ gui/groove_settings_dialog.ui \ gui/configuration_dialog.ui \ gui/comment_edit_dialog.ui \ gui/vgm_export_settings_dialog.ui \ gui/wave_export_settings_dialog.ui \ gui/instrument_selection_dialog.ui \ gui/s98_export_settings_dialog.ui \ gui/fm_envelope_set_edit_dialog.ui INCLUDEPATH += \ $$PWD/chips \ $$PWD/stream \ $$PWD/instrument \ $$PWD/command \ $$PWD/module \ $$PWD/io RESOURCES += \ bamboo_tracker.qrc TRANSLATIONS += \ res/lang/bamboo_tracker_fr.ts \ res/lang/bamboo_tracker_ja.ts include("stream/RtAudio/RtAudio.pri") include("midi/RtMidi/RtMidi.pri") equals(QT_MAJOR_VERSION, 5) { lessThan(QT_MINOR_VERSION, 12) { message(using a workaround for missing 'lrelease' option in Qt <5.12...) for(tsfile, TRANSLATIONS) { qmfile = $$tsfile qmfile ~= s/.ts$/.qm/ qmfile ~= s,^res/lang,.qm, thisqmcom = $${qmfile}.commands win32:$$thisqmcom = mkdir .qm; else:$$thisqmcom = test -d .qm || mkdir -p .qm; $$thisqmcom += lrelease -qm $$qmfile $$PWD/$$tsfile thisqmdep = $${qmfile}.depends $$thisqmdep = $$PWD/$${tsfile} PRE_TARGETDEPS += $${qmfile} QMAKE_EXTRA_TARGETS += $${qmfile} thisinst = translations_$${qmfile} thisinstdep = $${thisinst}.depends $$thisinstdep = $$qmfile thisinstcfg = $${thisinst}.CONFIG $$thisinstcfg = no_check_exist thisinstfil = $${thisinst}.files $$thisinstfil = $$PWD/$$qmfile thisinstpat = $${thisinst}.path $$thisinstpat = $$QM_FILES_INSTALL_PATH INSTALLS += $$thisinst } } else { CONFIG += lrelease } } win32 { RC_ICONS = res/icon/BambooTracker.ico } BambooTracker-0.3.5/BambooTracker/bamboo_tracker.cpp000066400000000000000000001567201362177441300224460ustar00rootroot00000000000000#include "bamboo_tracker.hpp" #include #include #include #include #include #include "commands.hpp" #include "io_handlers.hpp" #include "bank.hpp" const uint32_t BambooTracker::CHIP_CLOCK = 3993600 * 2; BambooTracker::BambooTracker(std::weak_ptr config) : instMan_(std::make_shared()), tickCounter_(std::make_shared()), mod_(std::make_shared()), octave_(4), curSongNum_(0), curTrackNum_(0), curOrderNum_(0), curStepNum_(0), curInstNum_(-1), isFollowPlay_(true) { opnaCtrl_ = std::make_shared( static_cast(config.lock()->getEmulator()), CHIP_CLOCK, config.lock()->getSampleRate(), config.lock()->getBufferLength()); setMasterVolume(config.lock()->getMixerVolumeMaster()); setMasterVolumeFM(config.lock()->getMixerVolumeFM()); setMasterVolumeSSG(config.lock()->getMixerVolumeSSG()); songStyle_ = mod_->getSong(curSongNum_).getStyle(); jamMan_ = std::make_unique(); playback_ = std::make_unique( opnaCtrl_, instMan_, tickCounter_, mod_, config.lock()->getRetrieveChannelState()); } /********** Change configuration **********/ void BambooTracker::changeConfiguration(std::weak_ptr config) { setStreamRate(static_cast(config.lock()->getSampleRate())); setStreamDuration(static_cast(config.lock()->getBufferLength())); setMasterVolume(config.lock()->getMixerVolumeMaster()); if (mod_->getMixerType() == MixerType::UNSPECIFIED) { setMasterVolumeFM(config.lock()->getMixerVolumeFM()); setMasterVolumeSSG(config.lock()->getMixerVolumeSSG()); } playback_->setChannelRetrieving(config.lock()->getRetrieveChannelState()); } /********** Change octave **********/ void BambooTracker::setCurrentOctave(int octave) { octave_ = octave; } int BambooTracker::getCurrentOctave() const { return octave_; } /********** Current track **********/ void BambooTracker::setCurrentTrack(int num) { curTrackNum_ = num; } TrackAttribute BambooTracker::getCurrentTrackAttribute() const { TrackAttribute ret = songStyle_.trackAttribs.at(static_cast(curTrackNum_)); return ret; } /********** Current instrument **********/ void BambooTracker::setCurrentInstrument(int n) { curInstNum_ = n; } int BambooTracker::getCurrentInstrumentNumber() const { return curInstNum_; } /********** Instrument edit **********/ void BambooTracker::addInstrument(int num, std::string name) { comMan_.invoke(std::make_unique( instMan_, num, songStyle_.trackAttribs[static_cast(curTrackNum_)].source, name)); } void BambooTracker::removeInstrument(int num) { comMan_.invoke(std::make_unique(instMan_, num)); } std::unique_ptr BambooTracker::getInstrument(int num) { std::shared_ptr inst = instMan_->getInstrumentSharedPtr(num); if (inst == nullptr) return std::unique_ptr(); else return inst->clone(); } void BambooTracker::cloneInstrument(int num, int refNum) { comMan_.invoke(std::make_unique(instMan_, num, refNum)); } void BambooTracker::deepCloneInstrument(int num, int refNum) { comMan_.invoke(std::make_unique(instMan_, num, refNum)); } void BambooTracker::loadInstrument(BinaryContainer& container, std::string path, int instNum) { auto inst = InstrumentIO::loadInstrument(container, path, instMan_, instNum); comMan_.invoke(std::make_unique( instMan_, std::unique_ptr(inst))); } void BambooTracker::saveInstrument(BinaryContainer& container, int instNum) { InstrumentIO::saveInstrument(container, instMan_, instNum); } void BambooTracker::importInstrument(const AbstractBank &bank, size_t index, int instNum) { auto inst = bank.loadInstrument(index, instMan_, instNum); comMan_.invoke(std::make_unique( instMan_, std::unique_ptr(inst))); } void BambooTracker::exportInstruments(BinaryContainer& container, std::vector instNums) { BankIO::saveBank(container, instNums, instMan_); } int BambooTracker::findFirstFreeInstrumentNumber() const { return instMan_->findFirstFreeInstrument(); } void BambooTracker::setInstrumentName(int num, std::string name) { comMan_.invoke(std::make_unique(instMan_, num, name)); } void BambooTracker::clearAllInstrument() { instMan_->clearAll(); } std::vector BambooTracker::getInstrumentIndices() const { return instMan_->getInstrumentIndices(); } std::vector BambooTracker::getUnusedInstrumentIndices() const { std::vector unused; std::unordered_set regdInsts = mod_->getRegisterdInstruments(); for (auto& inst : instMan_->getInstrumentIndices()) { if (!regdInsts.count(inst)) unused.push_back(inst); } return unused; } void BambooTracker::clearUnusedInstrumentProperties() { instMan_->clearUnusedInstrumentProperties(); } std::vector BambooTracker::getInstrumentNames() const { return instMan_->getInstrumentNameList(); } std::vector> BambooTracker::checkDuplicateInstruments() const { return instMan_->checkDuplicateInstruments(); } //--- FM void BambooTracker::setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value) { instMan_->setEnvelopeFMParameter(envNum, param, value); opnaCtrl_->updateInstrumentFMEnvelopeParameter(envNum, param); } void BambooTracker::setEnvelopeFMOperatorEnable(int envNum, int opNum, bool enable) { instMan_->setEnvelopeFMOperatorEnabled(envNum, opNum, enable); opnaCtrl_->setInstrumentFMOperatorEnabled(envNum, opNum); } void BambooTracker::setInstrumentFMEnvelope(int instNum, int envNum) { instMan_->setInstrumentFMEnvelope(instNum, envNum); opnaCtrl_->updateInstrumentFM(instNum); } std::vector BambooTracker::getEnvelopeFMUsers(int envNum) const { return instMan_->getEnvelopeFMUsers(envNum); } void BambooTracker::setLFOFMParameter(int lfoNum, FMLFOParameter param, int value) { instMan_->setLFOFMParameter(lfoNum, param, value); opnaCtrl_->updateInstrumentFMLFOParameter(lfoNum, param); } void BambooTracker::setInstrumentFMLFOEnabled(int instNum, bool enabled) { instMan_->setInstrumentFMLFOEnabled(instNum, enabled); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMLFO(int instNum, int lfoNum) { instMan_->setInstrumentFMLFO(instNum, lfoNum); opnaCtrl_->updateInstrumentFM(instNum); } std::vector BambooTracker::getLFOFMUsers(int lfoNum) const { return instMan_->getLFOFMUsers(lfoNum); } void BambooTracker::addOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int type, int data) { instMan_->addOperatorSequenceFMSequenceCommand(param, opSeqNum, type, data); } void BambooTracker::removeOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum) { instMan_->removeOperatorSequenceFMSequenceCommand(param, opSeqNum); } void BambooTracker::setOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int cnt, int type, int data) { instMan_->setOperatorSequenceFMSequenceCommand(param, opSeqNum, cnt, type, data); } void BambooTracker::setOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setOperatorSequenceFMLoops(param, opSeqNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, ReleaseType type, int begin) { instMan_->setOperatorSequenceFMRelease(param, opSeqNum, type, begin); } void BambooTracker::setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum) { instMan_->setInstrumentFMOperatorSequence(instNum, param, opSeqNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled) { instMan_->setInstrumentFMOperatorSequenceEnabled(instNum, param, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::vector BambooTracker::getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const { return instMan_->getOperatorSequenceFMUsers(param, opSeqNum); } void BambooTracker::setArpeggioFMType(int arpNum, SequenceType type) { instMan_->setArpeggioFMType(arpNum, type); } void BambooTracker::addArpeggioFMSequenceCommand(int arpNum, int type, int data) { instMan_->addArpeggioFMSequenceCommand(arpNum, type, data); } void BambooTracker::removeArpeggioFMSequenceCommand(int arpNum) { instMan_->removeArpeggioFMSequenceCommand(arpNum); } void BambooTracker::setArpeggioFMSequenceCommand(int arpNum, int cnt, int type, int data) { instMan_->setArpeggioFMSequenceCommand(arpNum, cnt, type, data); } void BambooTracker::setArpeggioFMLoops(int arpNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setArpeggioFMLoops(arpNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setArpeggioFMRelease(int arpNum, ReleaseType type, int begin) { instMan_->setArpeggioFMRelease(arpNum, type, begin); } void BambooTracker::setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum) { instMan_->setInstrumentFMArpeggio(instNum, op, arpNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMArpeggioEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::vector BambooTracker::getArpeggioFMUsers(int arpNum) const { return instMan_->getArpeggioFMUsers(arpNum); } void BambooTracker::setPitchFMType(int ptNum, SequenceType type) { instMan_->setPitchFMType(ptNum, type); } void BambooTracker::addPitchFMSequenceCommand(int ptNum, int type, int data) { instMan_->addPitchFMSequenceCommand(ptNum, type, data); } void BambooTracker::removePitchFMSequenceCommand(int ptNum) { instMan_->removePitchFMSequenceCommand(ptNum); } void BambooTracker::setPitchFMSequenceCommand(int ptNum, int cnt, int type, int data) { instMan_->setPitchFMSequenceCommand(ptNum, cnt, type, data); } void BambooTracker::setPitchFMLoops(int ptNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setPitchFMLoops(ptNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setPitchFMRelease(int ptNum, ReleaseType type, int begin) { instMan_->setPitchFMRelease(ptNum, type, begin); } void BambooTracker::setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum) { instMan_->setInstrumentFMPitch(instNum, op, ptNum); opnaCtrl_->updateInstrumentFM(instNum); } void BambooTracker::setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMPitchEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } std::vector BambooTracker::getPitchFMUsers(int ptNum) const { return instMan_->getPitchFMUsers(ptNum); } void BambooTracker::setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled) { instMan_->setInstrumentFMEnvelopeResetEnabled(instNum, op, enabled); opnaCtrl_->updateInstrumentFM(instNum); } //--- SSG void BambooTracker::addWaveFormSSGSequenceCommand(int wfNum, int type, int data) { instMan_->addWaveFormSSGSequenceCommand(wfNum, type, data); } void BambooTracker::removeWaveFormSSGSequenceCommand(int wfNum) { instMan_->removeWaveFormSSGSequenceCommand(wfNum); } void BambooTracker::setWaveFormSSGSequenceCommand(int wfNum, int cnt, int type, int data) { instMan_->setWaveFormSSGSequenceCommand(wfNum, cnt, type, data); } void BambooTracker::setWaveFormSSGLoops(int wfNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setWaveFormSSGLoops(wfNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setWaveFormSSGRelease(int wfNum, ReleaseType type, int begin) { instMan_->setWaveFormSSGRelease(wfNum, type, begin); } void BambooTracker::setInstrumentSSGWaveForm(int instNum, int wfNum) { instMan_->setInstrumentSSGWaveForm(instNum, wfNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGWaveFormEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGWaveFormEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::vector BambooTracker::getWaveFormSSGUsers(int wfNum) const { return instMan_->getWaveFormSSGUsers(wfNum); } void BambooTracker::addToneNoiseSSGSequenceCommand(int tnNum, int type, int data) { instMan_->addToneNoiseSSGSequenceCommand(tnNum, type, data); } void BambooTracker::removeToneNoiseSSGSequenceCommand(int tnNum) { instMan_->removeToneNoiseSSGSequenceCommand(tnNum); } void BambooTracker::setToneNoiseSSGSequenceCommand(int tnNum, int cnt, int type, int data) { instMan_->setToneNoiseSSGSequenceCommand(tnNum, cnt, type, data); } void BambooTracker::setToneNoiseSSGLoops(int tnNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setToneNoiseSSGLoops(tnNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setToneNoiseSSGRelease(int tnNum, ReleaseType type, int begin) { instMan_->setToneNoiseSSGRelease(tnNum, type, begin); } void BambooTracker::setInstrumentSSGToneNoise(int instNum, int tnNum) { instMan_->setInstrumentSSGToneNoise(instNum, tnNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGToneNoiseEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::vector BambooTracker::getToneNoiseSSGUsers(int tnNum) const { return instMan_->getToneNoiseSSGUsers(tnNum); } void BambooTracker::addEnvelopeSSGSequenceCommand(int envNum, int type, int data) { instMan_->addEnvelopeSSGSequenceCommand(envNum, type, data); } void BambooTracker::removeEnvelopeSSGSequenceCommand(int envNum) { instMan_->removeEnvelopeSSGSequenceCommand(envNum); } void BambooTracker::setEnvelopeSSGSequenceCommand(int envNum, int cnt, int type, int data) { instMan_->setEnvelopeSSGSequenceCommand(envNum, cnt, type, data); } void BambooTracker::setEnvelopeSSGLoops(int envNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setEnvelopeSSGLoops(envNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setEnvelopeSSGRelease(int envNum, ReleaseType type, int begin) { instMan_->setEnvelopeSSGRelease(envNum, type, begin); } void BambooTracker::setInstrumentSSGEnvelope(int instNum, int envNum) { instMan_->setInstrumentSSGEnvelope(instNum, envNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGEnvelopeEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::vector BambooTracker::getEnvelopeSSGUsers(int envNum) const { return instMan_->getEnvelopeSSGUsers(envNum); } void BambooTracker::setArpeggioSSGType(int arpNum, SequenceType type) { instMan_->setArpeggioSSGType(arpNum, type); } void BambooTracker::addArpeggioSSGSequenceCommand(int arpNum, int type, int data) { instMan_->addArpeggioSSGSequenceCommand(arpNum, type, data); } void BambooTracker::removeArpeggioSSGSequenceCommand(int arpNum) { instMan_->removeArpeggioSSGSequenceCommand(arpNum); } void BambooTracker::setArpeggioSSGSequenceCommand(int arpNum, int cnt, int type, int data) { instMan_->setArpeggioSSGSequenceCommand(arpNum, cnt, type, data); } void BambooTracker::setArpeggioSSGLoops(int arpNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setArpeggioSSGLoops(arpNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setArpeggioSSGRelease(int arpNum, ReleaseType type, int begin) { instMan_->setArpeggioSSGRelease(arpNum, type, begin); } void BambooTracker::setInstrumentSSGArpeggio(int instNum, int arpNum) { instMan_->setInstrumentSSGArpeggio(instNum, arpNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGArpeggioEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGArpeggioEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::vector BambooTracker::getArpeggioSSGUsers(int arpNum) const { return instMan_->getArpeggioSSGUsers(arpNum); } void BambooTracker::setPitchSSGType(int ptNum, SequenceType type) { instMan_->setPitchSSGType(ptNum, type); } void BambooTracker::addPitchSSGSequenceCommand(int ptNum, int type, int data) { instMan_->addPitchSSGSequenceCommand(ptNum, type, data); } void BambooTracker::removePitchSSGSequenceCommand(int ptNum) { instMan_->removePitchSSGSequenceCommand(ptNum); } void BambooTracker::setPitchSSGSequenceCommand(int ptNum, int cnt, int type, int data) { instMan_->setPitchSSGSequenceCommand(ptNum, cnt, type, data); } void BambooTracker::setPitchSSGLoops(int ptNum, std::vector begins, std::vector ends, std::vector times) { instMan_->setPitchSSGLoops(ptNum, std::move(begins), std::move(ends), std::move(times)); } void BambooTracker::setPitchSSGRelease(int ptNum, ReleaseType type, int begin) { instMan_->setPitchSSGRelease(ptNum, type, begin); } void BambooTracker::setInstrumentSSGPitch(int instNum, int ptNum) { instMan_->setInstrumentSSGPitch(instNum, ptNum); opnaCtrl_->updateInstrumentSSG(instNum); } void BambooTracker::setInstrumentSSGPitchEnabled(int instNum, bool enabled) { instMan_->setInstrumentSSGPitchEnabled(instNum, enabled); opnaCtrl_->updateInstrumentSSG(instNum); } std::vector BambooTracker::getPitchSSGUsers(int ptNum) const { return instMan_->getPitchSSGUsers(ptNum); } /********** Song edit **********/ int BambooTracker::getCurrentSongNumber() const { return curSongNum_; } void BambooTracker::setCurrentSongNumber(int num) { curSongNum_ = num; curTrackNum_ = 0; curOrderNum_ = 0; curStepNum_ = 0; auto& song = mod_->getSong(curSongNum_); songStyle_ = song.getStyle(); playback_->setSong(mod_, curSongNum_); /*jamMan_->clear();*/ // Reset opnaCtrl_->reset(); opnaCtrl_->setMode(songStyle_.type); tickCounter_->resetCount(); tickCounter_->setTempo(song.getTempo()); tickCounter_->setSpeed(song.getSpeed()); tickCounter_->setGroove(mod_->getGroove(song.getGroove()).getSequence()); tickCounter_->setGrooveTrigger(song.isUsedTempo() ? GrooveTrigger::Invalid : GrooveTrigger::ValidByGlobal); size_t fmch = getFMChannelCount(songStyle_.type); muteStateFM_ = std::vector(fmch, false); for (int i = 0; i < static_cast(fmch); ++i) opnaCtrl_->setMuteFMState(i, false); muteStateSSG_ = std::vector(3, false); for (int i = 0; i < 3; ++i) opnaCtrl_->setMuteSSGState(i, false); muteStateDrum_ = std::vector(6, false); for (int i = 0; i < 6; ++i) opnaCtrl_->setMuteDrumState(i, false); } /********** Order edit **********/ int BambooTracker::getCurrentOrderNumber() const { return curOrderNum_; } void BambooTracker::setCurrentOrderNumber(int num) { curOrderNum_ = num; } /********** Pattern edit **********/ int BambooTracker::getCurrentStepNumber() const { return curStepNum_; } void BambooTracker::setCurrentStepNumber(int num) { curStepNum_ = num; } /********** Undo-Redo **********/ void BambooTracker::undo() { comMan_.undo(); } void BambooTracker::redo() { comMan_.redo(); } void BambooTracker::clearCommandHistory() { comMan_.clear(); } /********** Jam mode **********/ void BambooTracker::toggleJamMode() { if (jamMan_->toggleJamMode() && !isPlaySong()) { jamMan_->polyphonic(true); } else { jamMan_->polyphonic(false); } } bool BambooTracker::isJamMode() const { return jamMan_->isJamMode(); } void BambooTracker::jamKeyOn(JamKey key) { int keyNum = octaveAndNoteToNoteNumber(octave_, JamManager::jamKeyToNote(key)); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOn(key, keyNum, attrib); } void BambooTracker::jamKeyOn(int keyNum) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOn(JamKey::MidiKey, keyNum, attrib); } void BambooTracker::jamKeyOnForced(JamKey key, SoundSource src) { int keyNum = octaveAndNoteToNoteNumber(octave_, JamManager::jamKeyToNote(key)); auto it = std::find_if(songStyle_.trackAttribs.begin(), songStyle_.trackAttribs.end(), [src](TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOn(key, keyNum, *it); } void BambooTracker::jamKeyOnForced(int keyNum, SoundSource src) { auto it = std::find_if(songStyle_.trackAttribs.begin(), songStyle_.trackAttribs.end(), [src](TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOn(JamKey::MidiKey, keyNum, *it); } void BambooTracker::funcJamKeyOn(JamKey key, int keyNum, const TrackAttribute& attrib) { if (attrib.source == SoundSource::DRUM) { opnaCtrl_->setKeyOnFlagDrum(attrib.channelInSource); opnaCtrl_->updateRegisterStates(); } else { std::vector&& list = jamMan_->keyOn(key, attrib.channelInSource, attrib.source, keyNum); if (list.size() == 2) { // Key off JamKeyData& offData = list[1]; switch (offData.source) { case SoundSource::FM: if (songStyle_.type == SongType::FM3chExpanded && offData.channelInSource == 2) { opnaCtrl_->keyOffFM(2, true); opnaCtrl_->keyOffFM(6, true); opnaCtrl_->keyOffFM(7, true); opnaCtrl_->keyOffFM(8, true); } else { opnaCtrl_->keyOffFM(offData.channelInSource, true); } break; case SoundSource::SSG: opnaCtrl_->keyOffSSG(offData.channelInSource, true); break; default: break; } } std::shared_ptr tmpInst = instMan_->getInstrumentSharedPtr(curInstNum_); JamKeyData& onData = list.front(); Note note; int octave, pitch; if (key == JamKey::MidiKey) { auto octNote = noteNumberToOctaveAndNote(onData.keyNum); note = octNote.second; octave = octNote.first; } else { note = JamManager::jamKeyToNote(onData.key); octave = JamManager::calcOctave(octave_, onData.key); if (octave > 7) { // Tone range check octave = 7; note = Note::B; } } pitch = 0; switch (onData.source) { case SoundSource::FM: if (auto fm = std::dynamic_pointer_cast(tmpInst)) opnaCtrl_->setInstrumentFM(onData.channelInSource, fm); if (songStyle_.type == SongType::FM3chExpanded && onData.channelInSource == 2) { opnaCtrl_->keyOnFM(2, note, octave, pitch, true); opnaCtrl_->keyOnFM(6, note, octave, pitch, true); opnaCtrl_->keyOnFM(7, note, octave, pitch, true); opnaCtrl_->keyOnFM(8, note, octave, pitch, true); } else { opnaCtrl_->keyOnFM(onData.channelInSource, note, octave, pitch, true); } break; case SoundSource::SSG: if (auto ssg = std::dynamic_pointer_cast(tmpInst)) opnaCtrl_->setInstrumentSSG(onData.channelInSource, ssg); opnaCtrl_->keyOnSSG(onData.channelInSource, note, octave, pitch, true); break; default: break; } } } void BambooTracker::jamKeyOff(JamKey key) { int keyNum = octaveAndNoteToNoteNumber(octave_, JamManager::jamKeyToNote(key)); const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOff(key, keyNum, attrib); } void BambooTracker::jamKeyOff(int keyNum) { const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(curTrackNum_)]; funcJamKeyOff(JamKey::MidiKey, keyNum, attrib); } void BambooTracker::jamKeyOffForced(JamKey key, SoundSource src) { int keyNum = octaveAndNoteToNoteNumber(octave_, JamManager::jamKeyToNote(key)); auto it = std::find_if(songStyle_.trackAttribs.begin(), songStyle_.trackAttribs.end(), [src](TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOff(key, keyNum, *it); } void BambooTracker::jamKeyOffForced(int keyNum, SoundSource src) { auto it = std::find_if(songStyle_.trackAttribs.begin(), songStyle_.trackAttribs.end(), [src](TrackAttribute& attrib) { return attrib.source == src; }); funcJamKeyOff(JamKey::MidiKey, keyNum, *it); } void BambooTracker::funcJamKeyOff(JamKey key, int keyNum, const TrackAttribute& attrib) { if (attrib.source == SoundSource::DRUM) { opnaCtrl_->setKeyOffFlagDrum(attrib.channelInSource); opnaCtrl_->updateRegisterStates(); } else { JamKeyData&& data = jamMan_->keyOff(key, keyNum); if (data.channelInSource > -1) { // Key still sound switch (data.source) { case SoundSource::FM: if (songStyle_.type == SongType::FM3chExpanded && data.channelInSource == 2) { opnaCtrl_->keyOffFM(2, true); opnaCtrl_->keyOffFM(6, true); opnaCtrl_->keyOffFM(7, true); opnaCtrl_->keyOffFM(8, true); } else { opnaCtrl_->keyOffFM(data.channelInSource, true); } break; case SoundSource::SSG: opnaCtrl_->keyOffSSG(data.channelInSource, true); break; default: break; } } } } /********** Play song **********/ void BambooTracker::startPlaySong() { playback_->startPlaySong(curOrderNum_); startPlay(); if (isFollowPlay_) curStepNum_ = 0; } void BambooTracker::startPlayFromStart() { playback_->startPlayFromStart(); startPlay(); if (isFollowPlay_) { curOrderNum_ = 0; curStepNum_ = 0; } } void BambooTracker::startPlayPattern() { playback_->startPlayPattern(curOrderNum_); startPlay(); if (isFollowPlay_) curStepNum_ = 0; } void BambooTracker::startPlayFromCurrentStep() { playback_->startPlayFromCurrentStep(curOrderNum_, curStepNum_); startPlay(); } void BambooTracker::startPlay() { jamMan_->polyphonic(false); for (size_t i = 0; i < muteStateFM_.size(); ++i) opnaCtrl_->setMuteFMState(static_cast(i), muteStateFM_[i]); for (size_t i = 0; i < muteStateSSG_.size(); ++i) opnaCtrl_->setMuteSSGState(static_cast(i), muteStateSSG_[i]); for (size_t i = 0; i < muteStateDrum_.size(); ++i) opnaCtrl_->setMuteDrumState(static_cast(i), muteStateDrum_[i]); } void BambooTracker::stopPlaySong() { playback_->stopPlaySong(); jamMan_->polyphonic(true); for (size_t i = 0; i < muteStateFM_.size(); ++i) opnaCtrl_->setMuteFMState(static_cast(i), false); for (size_t i = 0; i < muteStateSSG_.size(); ++i) opnaCtrl_->setMuteSSGState(static_cast(i), false); for (size_t i = 0; i < muteStateDrum_.size(); ++i) opnaCtrl_->setMuteDrumState(static_cast(i), false); } bool BambooTracker::isPlaySong() const { return playback_->isPlaySong(); } PlaybackState BambooTracker::getPlaybackState() const { return playback_->getPlaybackState(); } void BambooTracker::setTrackMuteState(int trackNum, bool isMute) { auto& ta = songStyle_.trackAttribs[static_cast(trackNum)]; int ch = ta.channelInSource; switch (ta.source) { case SoundSource::FM: muteStateFM_.at(static_cast(ch)) = isMute; if (isPlaySong()) opnaCtrl_->setMuteFMState(ch, isMute); break; case SoundSource::SSG: muteStateSSG_.at(static_cast(ch)) = isMute; if (isPlaySong()) opnaCtrl_->setMuteSSGState(ch, isMute); break; case SoundSource::DRUM: muteStateDrum_.at(static_cast(ch)) = isMute; if (isPlaySong()) opnaCtrl_->setMuteDrumState(ch, isMute); break; } } bool BambooTracker::isMute(int trackNum) { auto& ta = songStyle_.trackAttribs[static_cast(trackNum)]; size_t ch = static_cast(ta.channelInSource); switch (ta.source) { case SoundSource::FM: return muteStateFM_.at(ch); case SoundSource::SSG: return muteStateSSG_.at(ch); case SoundSource::DRUM: return muteStateDrum_.at(ch); default: return false; // Dummy } } void BambooTracker::setFollowPlay(bool isFollowed) { isFollowPlay_ = isFollowed; if (isFollowed) { int odr = playback_->getPlayingOrderNumber(); if (odr >= 0) { curOrderNum_ = odr; curStepNum_ = playback_->getPlayingStepNumber(); } } } bool BambooTracker::isFollowPlay() const { return isFollowPlay_; } int BambooTracker::getPlayingOrderNumber() const { return playback_->getPlayingOrderNumber(); } int BambooTracker::getPlayingStepNumber() const { return playback_->getPlayingStepNumber(); } /********** Export **********/ bool BambooTracker::exportToWav(BinaryContainer& container, int rate, int loopCnt, std::function bar) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(rate); size_t sampCnt = static_cast(opnaCtrl_->getRate() * opnaCtrl_->getDuration() / 1000); size_t intrCnt = static_cast(opnaCtrl_->getRate()) / mod_->getTickFrequency(); size_t intrCntRest = 0; std::vector dumbuf(sampCnt << 1); int endOrder = 0; int endStep = 0; checkNextPositionOfLastStep(endOrder, endStep); bool endFlag = false; bool tmpFollow = std::exchange(isFollowPlay_, false); std::shared_ptr exCntr = std::make_shared(); opnaCtrl_->setExportContainer(exCntr); startPlayFromStart(); while (true) { size_t sampCntRest = sampCnt; while (sampCntRest) { if (!intrCntRest) { // Interruption intrCntRest = intrCnt; // Set counts to next interruption if (!streamCountUp()) { if (bar()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if ((playOrder == -1 && playStep == -1) || (playOrder == endOrder && playStep == endStep && !(loopCnt--))){ endFlag = true; break; } } } size_t count = std::min(intrCntRest, sampCntRest); sampCntRest -= count; intrCntRest -= count; opnaCtrl_->getStreamSamples(&dumbuf[0], count); } if (endFlag) break; } opnaCtrl_->setExportContainer(); stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); try { ExportHandler::writeWave(container, exCntr->getStream(), static_cast(rate)); return true; } catch (...) { throw; } } bool BambooTracker::exportToVgm(BinaryContainer& container, int target, bool gd3TagEnabled, GD3Tag tag, std::function bar) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(44100); double dblIntrCnt = 44100.0 / static_cast(mod_->getTickFrequency()); size_t intrCnt = static_cast(dblIntrCnt); double intrCntDiff = dblIntrCnt - intrCnt; double intrCntRest = 0; std::vector dumbuf((intrCnt + 1) << 1); int loopOrder = 0; int loopStep = 0; checkNextPositionOfLastStep(loopOrder, loopStep); bool loopFlag = (loopOrder != -1); int endCnt = (loopOrder == -1) ? 0 : 1; bool tmpFollow = std::exchange(isFollowPlay_, false); uint32_t loopPoint = 0; uint32_t loopPointSamples = 0; std::shared_ptr exCntr = std::make_shared(target, mod_->getTickFrequency()); opnaCtrl_->setExportContainer(exCntr); startPlayFromStart(); exCntr->forceMoveLoopPoint(); while (true) { if (!streamCountUp()) { if (bar()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if (playOrder == loopOrder && playStep == loopStep && !(endCnt--)) break; if (loopFlag && loopOrder == playOrder && loopStep == playStep) { loopPoint = exCntr->setLoopPoint(); loopPointSamples = exCntr->getSampleLength(); } } intrCntRest += intrCntDiff; size_t extraIntrCnt = static_cast(intrCntRest); intrCntRest -= extraIntrCnt; opnaCtrl_->getStreamSamples(&dumbuf[0], intrCnt + extraIntrCnt); } opnaCtrl_->setExportContainer(); stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); try { ExportHandler::writeVgm(container, target, exCntr->getData(), CHIP_CLOCK, mod_->getTickFrequency(), loopFlag, loopPoint, exCntr->getSampleLength() - loopPointSamples, exCntr->getSampleLength(), gd3TagEnabled, tag); return true; } catch (...) { throw; } } bool BambooTracker::exportToS98(BinaryContainer& container, int target, bool tagEnabled, S98Tag tag, int rate, std::function bar) { int tmpRate = opnaCtrl_->getRate(); opnaCtrl_->setRate(rate); double dblIntrCnt = static_cast(rate) / static_cast(mod_->getTickFrequency()); size_t intrCnt = static_cast(dblIntrCnt); double intrCntDiff = dblIntrCnt - intrCnt; double intrCntRest = 0; std::vector dumbuf((intrCnt + 1) << 1); int loopOrder = 0; int loopStep = 0; checkNextPositionOfLastStep(loopOrder, loopStep); bool loopFlag = (loopOrder != -1); int endCnt = (loopOrder == -1) ? 0 : 1; bool tmpFollow = std::exchange(isFollowPlay_, false); uint32_t loopPoint = 0; std::shared_ptr exCntr = std::make_shared(target); opnaCtrl_->setExportContainer(exCntr); startPlayFromStart(); exCntr->forceMoveLoopPoint(); while (true) { exCntr->getData(); // Set wait counts if (!streamCountUp()) { if (bar()) { // Update lambda function stopPlaySong(); isFollowPlay_ = tmpFollow; return false; } int playOrder = playback_->getPlayingOrderNumber(); int playStep = playback_->getPlayingStepNumber(); if (playOrder == loopOrder && playStep == loopStep && !(endCnt--)) break; if (loopFlag && loopOrder == playOrder && loopStep == playStep) { loopPoint = exCntr->setLoopPoint(); } } intrCntRest += intrCntDiff; size_t extraIntrCnt = static_cast(intrCntRest); intrCntRest -= extraIntrCnt; opnaCtrl_->getStreamSamples(&dumbuf[0], intrCnt + extraIntrCnt); } opnaCtrl_->setExportContainer(); stopPlaySong(); isFollowPlay_ = tmpFollow; opnaCtrl_->setRate(tmpRate); try { ExportHandler::writeS98(container, target, exCntr->getData(), CHIP_CLOCK, static_cast(rate), loopFlag, loopPoint, tagEnabled, tag); return true; } catch (...) { throw; } } void BambooTracker::checkNextPositionOfLastStep(int& endOrder, int& endStep) const { Song& song = mod_->getSong(curSongNum_); int lastOrder = static_cast(song.getOrderSize()) - 1; int lastStep = static_cast(getPatternSizeFromOrderNumber(curSongNum_, lastOrder)) - 1; endOrder = 0; endStep = 0; for (auto attrib : songStyle_.trackAttribs) { Step& step = song.getTrack(attrib.number).getPatternFromOrderNumber(lastOrder).getStep(lastStep); for (int i = 0; i < 4; ++i) { Effect&& eff = Effect::makeEffectData(attrib.source, step.getEffectID(i), step.getEffectValue(i)); switch (eff.type) { case EffectType::PositionJump: if (eff.value <= lastOrder) { endOrder = eff.value; endStep = 0; } break; case EffectType::SongEnd: endOrder = -1; endStep = -1; break; case EffectType::PatternBreak: if (eff.value < static_cast(getPatternSizeFromOrderNumber(curSongNum_, 0))) { endOrder = 0; endStep = eff.value; } break; default: break; } } } } /********** Real chip interface **********/ void BambooTracker::useSCCI(scci::SoundInterfaceManager* manager) { opnaCtrl_->useSCCI(manager); } void BambooTracker::useC86CTL(C86ctlBase* base) { opnaCtrl_->useC86CTL(base); } RealChipInterface BambooTracker::getRealChipinterface() const { if (opnaCtrl_->isUsedSCCI()) return RealChipInterface::SCCI; else if (opnaCtrl_->isUsedC86CTL()) return RealChipInterface::C86CTL; else return RealChipInterface::NONE; } /********** Stream events **********/ int BambooTracker::streamCountUp() { int state = playback_->streamCountUp(); if (!state && isFollowPlay_) { // Step int odr = playback_->getPlayingOrderNumber(); if (odr >= 0) { curOrderNum_ = odr; curStepNum_ = playback_->getPlayingStepNumber(); } } return state; } void BambooTracker::getStreamSamples(int16_t *container, size_t nSamples) { opnaCtrl_->getStreamSamples(container, nSamples); } void BambooTracker::killSound() { jamMan_->clear(); opnaCtrl_->reset(); } /********** Stream details **********/ int BambooTracker::getStreamRate() const { return opnaCtrl_->getRate(); } void BambooTracker::setStreamRate(int rate) { opnaCtrl_->setRate(rate); } int BambooTracker::getStreamDuration() const { return opnaCtrl_->getDuration(); } void BambooTracker::setStreamDuration(int duration) { opnaCtrl_->setDuration(duration); } int BambooTracker::getStreamTempo() const { return tickCounter_->getTempo(); } int BambooTracker::getStreamSpeed() const { return tickCounter_->getSpeed(); } bool BambooTracker::getStreamGrooveEnabled() const { return tickCounter_->getGrooveEnabled(); } void BambooTracker::setMasterVolume(int percentage) { opnaCtrl_->setMasterVolume(percentage); } void BambooTracker::setMasterVolumeFM(double dB) { opnaCtrl_->setMasterVolumeFM(dB); } void BambooTracker::setMasterVolumeSSG(double dB) { opnaCtrl_->setMasterVolumeSSG(dB); } /********** Module details **********/ /*----- Module -----*/ void BambooTracker::makeNewModule() { stopPlaySong(); clearAllInstrument(); opnaCtrl_->reset(); mod_ = std::make_shared(); tickCounter_->setInterruptRate(mod_->getTickFrequency()); setCurrentSongNumber(0); curInstNum_ = -1; clearCommandHistory(); } void BambooTracker::loadModule(BinaryContainer& container) { makeNewModule(); std::exception_ptr ep; try { ModuleIO::loadModule(container, mod_, instMan_); } catch (...) { ep = std::current_exception(); } tickCounter_->setInterruptRate(mod_->getTickFrequency()); setCurrentSongNumber(0); clearCommandHistory(); if (ep) std::rethrow_exception(ep); } void BambooTracker::saveModule(BinaryContainer& container) { ModuleIO::saveModule(container, mod_, instMan_); } void BambooTracker::setModulePath(std::string path) { mod_->setFilePath(path); } std::string BambooTracker::getModulePath() const { return mod_->getFilePath(); } void BambooTracker::setModuleTitle(std::string title) { mod_->setTitle(title); } std::string BambooTracker::getModuleTitle() const { return mod_->getTitle(); } void BambooTracker::setModuleAuthor(std::string author) { mod_->setAuthor(author); } std::string BambooTracker::getModuleAuthor() const { return mod_->getAuthor(); } void BambooTracker::setModuleCopyright(std::string copyright) { mod_->setCopyright(copyright); } std::string BambooTracker::getModuleCopyright() const { return mod_->getCopyright(); } void BambooTracker::setModuleComment(std::string comment) { mod_->setComment(comment); } std::string BambooTracker::getModuleComment() const { return mod_->getComment(); } void BambooTracker::setModuleTickFrequency(unsigned int freq) { mod_->setTickFrequency(freq); tickCounter_->setInterruptRate(freq); } unsigned int BambooTracker::getModuleTickFrequency() const { return mod_->getTickFrequency(); } void BambooTracker::setModuleStepHighlight1Distance(size_t dist) { mod_->setStepHighlight1Distance(dist); } size_t BambooTracker::getModuleStepHighlight1Distance() const { return mod_->getStepHighlight1Distance(); } void BambooTracker::setModuleStepHighlight2Distance(size_t dist) { mod_->setStepHighlight2Distance(dist); } size_t BambooTracker::getModuleStepHighlight2Distance() const { return mod_->getStepHighlight2Distance(); } void BambooTracker::setModuleMixerType(MixerType type) { mod_->setMixerType(type); } MixerType BambooTracker::getModuleMixerType() const { return mod_->getMixerType(); } void BambooTracker::setModuleCustomMixerFMLevel(double level) { mod_->setCustomMixerFMLevel(level); } double BambooTracker::getModuleCustomMixerFMLevel() const { return mod_->getCustomMixerFMLevel(); } void BambooTracker::setModuleCustomMixerSSGLevel(double level) { mod_->setCustomMixerSSGLevel(level); } double BambooTracker::getModuleCustomMixerSSGLevel() const { return mod_->getCustomMixerSSGLevel(); } size_t BambooTracker::getGrooveCount() const { return mod_->getGrooveCount(); } void BambooTracker::setGroove(int num, std::vector seq) { mod_->setGroove(num, std::move(seq)); } void BambooTracker::setGrooves(std::vector> seqs) { mod_->setGrooves(std::move(seqs)); } std::vector BambooTracker::getGroove(int num) const { return mod_->getGroove(num).getSequence(); } void BambooTracker::clearUnusedPatterns() { mod_->clearUnusedPatterns(); } void BambooTracker::replaceDuplicateInstrumentsInPatterns(std::vector> list) { std::unordered_map map; for (auto& group : list) { for (size_t i = 1; i < group.size(); ++i) { map.emplace(group[i], group.front()); } } mod_->replaceDuplicateInstrumentsInPatterns(map); } /*----- Song -----*/ void BambooTracker::setSongTitle(int songNum, std::string title) { mod_->getSong(songNum).setTitle(title); } std::string BambooTracker::getSongTitle(int songNum) const { return mod_->getSong(songNum).getTitle(); } void BambooTracker::setSongTempo(int songNum, int tempo) { mod_->getSong(songNum).setTempo(tempo); if (curSongNum_ == songNum) tickCounter_->setTempo(tempo); } int BambooTracker::getSongTempo(int songNum) const { return mod_->getSong(songNum).getTempo(); } void BambooTracker::setSongGroove(int songNum, int groove) { mod_->getSong(songNum).setGroove(groove); tickCounter_->setGroove(mod_->getGroove(groove).getSequence()); } int BambooTracker::getSongGroove(int songNum) const { return mod_->getSong(songNum).getGroove(); } void BambooTracker::toggleTempoOrGrooveInSong(int songNum, bool isTempo) { mod_->getSong(songNum).toggleTempoOrGroove(isTempo); tickCounter_->setGrooveTrigger(isTempo ? GrooveTrigger::Invalid : GrooveTrigger::ValidByGlobal); } bool BambooTracker::isUsedTempoInSong(int songNum) const { return mod_->getSong(songNum).isUsedTempo(); } SongStyle BambooTracker::getSongStyle(int songNum) const { return mod_->getSong(songNum).getStyle(); } void BambooTracker::setSongSpeed(int songNum, int speed) { mod_->getSong(songNum).setSpeed(speed); if (curSongNum_ == songNum) tickCounter_->setSpeed(speed); } int BambooTracker::getSongSpeed(int songNum) const { return mod_->getSong(songNum).getSpeed(); } size_t BambooTracker::getSongCount() const { return mod_->getSongCount(); } void BambooTracker::addSong(SongType songType, std::string title) { mod_->addSong(songType, title); } void BambooTracker::sortSongs(std::vector numbers) { mod_->sortSongs(std::move(numbers)); } size_t BambooTracker::getAllStepCount(int songNum, int loopCnt) const { int os = static_cast(getOrderSize(songNum)); int loopOrder = 0; int loopStep = 0; checkNextPositionOfLastStep(loopOrder, loopStep); if (loopOrder == -1) { size_t stepCnt = 0; for (int i = 0; i < os; ++i) stepCnt += getPatternSizeFromOrderNumber(songNum, i); return stepCnt; } else { size_t introStepCnt = 0; for (int i = 0; i < loopOrder; ++i) introStepCnt += getPatternSizeFromOrderNumber(curSongNum_, i); introStepCnt += static_cast(loopStep); size_t loopStepCnt = getPatternSizeFromOrderNumber(curSongNum_, loopOrder) - static_cast(loopStep); for (int i = loopOrder + 1; i < os; ++i) { loopStepCnt += getPatternSizeFromOrderNumber(songNum, i); } return introStepCnt + loopStepCnt * static_cast(loopCnt); } } /*----- Track -----*/ void BambooTracker::setEffectDisplayWidth(int songNum, int trackNum, size_t w) { mod_->getSong(songNum).getTrack(trackNum).setEffectDisplayWidth(w); } size_t BambooTracker::getEffectDisplayWidth(int songNum, int trackNum) const { return mod_->getSong(songNum).getTrack(trackNum).getEffectDisplayWidth(); } /*----- Order -----*/ std::vector BambooTracker::getOrderData(int songNum, int orderNum) const { return mod_->getSong(songNum).getOrderData(orderNum); } void BambooTracker::setOrderPatternDigit(int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, patternNum, secondEntry)); } void BambooTracker::insertOrderBelow(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } void BambooTracker::deleteOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } void BambooTracker::pasteOrderCells(int songNum, int beginTrack, int beginOrder, std::vector> cells) { // Arrange data std::vector> d; size_t w = songStyle_.trackAttribs.size() - static_cast(beginTrack); size_t h = getOrderSize(songNum) - static_cast(beginOrder); size_t width = std::min(cells.at(0).size(), w); size_t height = std::min(cells.size(), h); for (size_t i = 0; i < height; ++i) { d.emplace_back(); for (size_t j = 0; j < width; ++j) { d.at(i).push_back(cells.at(i).at(j)); } } comMan_.invoke(std::make_unique(mod_, songNum, beginTrack, beginOrder, std::move(d))); } void BambooTracker::duplicateOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } void BambooTracker::MoveOrder(int songNum, int orderNum, bool isUp) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum, isUp)); } void BambooTracker::clonePatterns(int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack) { comMan_.invoke(std::make_unique(mod_, songNum, beginOrder, beginTrack, endOrder, endTrack)); } void BambooTracker::cloneOrder(int songNum, int orderNum) { comMan_.invoke(std::make_unique(mod_, songNum, orderNum)); } size_t BambooTracker::getOrderSize(int songNum) const { return mod_->getSong(songNum).getOrderSize(); } bool BambooTracker::canAddNewOrder(int songNum) const { return mod_->getSong(songNum).canAddNewOrder(); } /*----- Pattern -----*/ int BambooTracker::getStepNoteNumber(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getNoteNumber(); } void BambooTracker::setStepNote(int songNum, int trackNum, int orderNum, int stepNum, int octave, Note note, bool autosetInst) { int nn = octaveAndNoteToNoteNumber(octave, note); int in = -1; if (autosetInst && curInstNum_ != -1 && (songStyle_.trackAttribs.at(static_cast(trackNum)).source == instMan_->getInstrumentSharedPtr(curInstNum_)->getSoundSource())) in = curInstNum_; comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, nn, autosetInst, in)); } void BambooTracker::setStepKeyOff(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::setEchoBufferAccess(int songNum, int trackNum, int orderNum, int stepNum, int bufNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, bufNum)); } void BambooTracker::eraseStepNote(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } int BambooTracker::getStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getInstrumentNumber(); } void BambooTracker::setStepInstrumentDigit(int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, instNum, secondEntry)); } void BambooTracker::eraseStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } int BambooTracker::getStepVolume(int songNum, int trackNum, int orderNum, int stepNum) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getVolume(); } void BambooTracker::setStepVolumeDigit(int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, volume, isFMReversed, secondEntry)); } void BambooTracker::eraseStepVolume(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } std::string BambooTracker::getStepEffectID(int songNum, int trackNum, int orderNum, int stepNum, int n) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectID(n); } void BambooTracker::setStepEffectIDCharacter(int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n, id, fillValue00, secondEntry)); } int BambooTracker::getStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) const { return mod_->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectValue(n); } void BambooTracker::setStepEffectValueDigit(int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n, value, ctrl, secondEntry)); } void BambooTracker::eraseStepEffect(int songNum, int trackNum, int orderNum, int stepNum, int n) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n)); } void BambooTracker::eraseStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum, n)); } void BambooTracker::insertStep(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::deletePreviousStep(int songNum, int trackNum, int orderNum, int stepNum) { comMan_.invoke(std::make_unique(mod_, songNum, trackNum, orderNum, stepNum)); } void BambooTracker::pastePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) { std::vector> d = arrangePatternDataCells(songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(cells)); comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(d))); } void BambooTracker::pasteMixPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) { std::vector> d = arrangePatternDataCells(songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(cells)); comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(d))); } void BambooTracker::pasteOverwritePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) { std::vector> d = arrangePatternDataCells(songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(cells)); comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, std::move(d))); } std::vector> BambooTracker::arrangePatternDataCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) { std::vector> d; size_t w = (songStyle_.trackAttribs.size() - static_cast(beginTrack) - 1) * 11 + (11 - static_cast(beginColmn)); size_t h = getPatternSizeFromOrderNumber(songNum, beginOrder) - static_cast(beginStep); size_t width = std::min(cells.at(0).size(), w); size_t height = std::min(cells.size(), h); for (size_t i = 0; i < height; ++i) { d.emplace_back(); for (size_t j = 0; j < width; ++j) { d.at(i).push_back(cells.at(i).at(j)); } } return d; } void BambooTracker::erasePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::increaseNoteKeyInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep)); } void BambooTracker::decreaseNoteKeyInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep)); } void BambooTracker::increaseNoteOctaveInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep)); } void BambooTracker::decreaseNoteOctaveInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep)); } void BambooTracker::expandPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::shrinkPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::interpolatePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::reversePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginColmn, beginOrder, beginStep, endTrack, endColmn, endStep)); } void BambooTracker::replaceInstrumentInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInstNum) { comMan_.invoke(std::make_unique( mod_, songNum, beginTrack, beginOrder, beginStep, endTrack, endStep, newInstNum)); } size_t BambooTracker::getPatternSizeFromOrderNumber(int songNum, int orderNum) const { return mod_->getSong(songNum).getPatternSizeFromOrderNumber(orderNum); } void BambooTracker::setDefaultPatternSize(int songNum, size_t size) { mod_->getSong(songNum).setDefaultPatternSize(size); playback_->checkPlayPosition(static_cast(size)); } size_t BambooTracker::getDefaultPatternSize(int songNum) const { return mod_->getSong(songNum).getDefaultPatternSize(); } void BambooTracker::getOutputHistory(int16_t* container) { opnaCtrl_->getOutputHistory(container); } BambooTracker-0.3.5/BambooTracker/bamboo_tracker.hpp000066400000000000000000000430241362177441300224430ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "configuration.hpp" #include "opna_controller.hpp" #include "jam_manager.hpp" #include "command_manager.hpp" #include "instruments_manager.hpp" #include "instrument.hpp" #include "tick_counter.hpp" #include "module.hpp" #include "song.hpp" #include "gd3_tag.hpp" #include "s98_tag.hpp" #include "chips/scci/scci.h" #include "chips/c86ctl/c86ctl_wrapper.hpp" #include "effect.hpp" #include "playback.hpp" #include "binary_container.hpp" #include "misc.hpp" class AbstractBank; class BambooTracker { public: explicit BambooTracker(std::weak_ptr config); // Change confuguration void changeConfiguration(std::weak_ptr config); // Change octave void setCurrentOctave(int octave); int getCurrentOctave() const; // Current track void setCurrentTrack(int num); TrackAttribute getCurrentTrackAttribute() const; // Current instrument void setCurrentInstrument(int n); int getCurrentInstrumentNumber() const; // Instrument edit void addInstrument(int num, std::string name); void removeInstrument(int num); std::unique_ptr getInstrument(int num); void cloneInstrument(int num, int refNum); void deepCloneInstrument(int num, int refNum); void loadInstrument(BinaryContainer& container, std::string path, int instNum); void saveInstrument(BinaryContainer& container, int instNum); void importInstrument(const AbstractBank &bank, size_t index, int instNum); void exportInstruments(BinaryContainer& container, std::vector instNums); int findFirstFreeInstrumentNumber() const; void setInstrumentName(int num, std::string name); void clearAllInstrument(); std::vector getInstrumentIndices() const; std::vector getUnusedInstrumentIndices() const; void clearUnusedInstrumentProperties(); std::vector getInstrumentNames() const; std::vector> checkDuplicateInstruments() const; //--- FM void setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value); void setEnvelopeFMOperatorEnable(int envNum, int opNum, bool enable); void setInstrumentFMEnvelope(int instNum, int envNum); std::vector getEnvelopeFMUsers(int envNum) const; void setLFOFMParameter(int lfoNum, FMLFOParameter param, int value); void setInstrumentFMLFOEnabled(int instNum, bool enabled); void setInstrumentFMLFO(int instNum, int lfoNum); std::vector getLFOFMUsers(int lfoNum) const; void addOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int type, int data); void removeOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int cnt, int type, int data); void setOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum, std::vector begins, std::vector ends, std::vector times); void setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, ReleaseType type, int begin); void setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum); void setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled); std::vector getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const; void setArpeggioFMType(int arpNum, SequenceType type); void addArpeggioFMSequenceCommand(int arpNum, int type, int data); void removeArpeggioFMSequenceCommand(int arpNum); void setArpeggioFMSequenceCommand(int arpNum, int cnt, int type, int data); void setArpeggioFMLoops(int arpNum, std::vector begins, std::vector ends, std::vector times); void setArpeggioFMRelease(int arpNum, ReleaseType type, int begin); void setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum); void setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled); std::vector getArpeggioFMUsers(int arpNum) const; void setPitchFMType(int ptNum, SequenceType type); void addPitchFMSequenceCommand(int ptNum, int type, int data); void removePitchFMSequenceCommand(int ptNum); void setPitchFMSequenceCommand(int ptNum, int cnt, int type, int data); void setPitchFMLoops(int ptNum, std::vector begins, std::vector ends, std::vector times); void setPitchFMRelease(int ptNum, ReleaseType type, int begin); void setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum); void setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled); std::vector getPitchFMUsers(int ptNum) const; void setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled); //--- SSG void addWaveFormSSGSequenceCommand(int wfNum, int type, int data); void removeWaveFormSSGSequenceCommand(int wfNum); void setWaveFormSSGSequenceCommand(int wfNum, int cnt, int type, int data); void setWaveFormSSGLoops(int wfNum, std::vector begins, std::vector ends, std::vector times); void setWaveFormSSGRelease(int wfNum, ReleaseType type, int begin); void setInstrumentSSGWaveForm(int instNum, int wfNum); void setInstrumentSSGWaveFormEnabled(int instNum, bool enabled); std::vector getWaveFormSSGUsers(int wfNum) const; void addToneNoiseSSGSequenceCommand(int tnNum, int type, int data); void removeToneNoiseSSGSequenceCommand(int tnNum); void setToneNoiseSSGSequenceCommand(int tnNum, int cnt, int type, int data); void setToneNoiseSSGLoops(int tnNum, std::vector begins, std::vector ends, std::vector times); void setToneNoiseSSGRelease(int tnNum, ReleaseType type, int begin); void setInstrumentSSGToneNoise(int instNum, int tnNum); void setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled); std::vector getToneNoiseSSGUsers(int tnNum) const; void addEnvelopeSSGSequenceCommand(int envNum, int type, int data); void removeEnvelopeSSGSequenceCommand(int envNum); void setEnvelopeSSGSequenceCommand(int envNum, int cnt, int type, int data); void setEnvelopeSSGLoops(int envNum, std::vector begins, std::vector ends, std::vector times); void setEnvelopeSSGRelease(int envNum, ReleaseType type, int begin); void setInstrumentSSGEnvelope(int instNum, int envNum); void setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled); std::vector getEnvelopeSSGUsers(int envNum) const; void setArpeggioSSGType(int arpNum, SequenceType type); void addArpeggioSSGSequenceCommand(int arpNum, int type, int data); void removeArpeggioSSGSequenceCommand(int arpNum); void setArpeggioSSGSequenceCommand(int arpNum, int cnt, int type, int data); void setArpeggioSSGLoops(int arpNum, std::vector begins, std::vector ends, std::vector times); void setArpeggioSSGRelease(int arpNum, ReleaseType type, int begin); void setInstrumentSSGArpeggio(int instNum, int arpNum); void setInstrumentSSGArpeggioEnabled(int instNum, bool enabled); std::vector getArpeggioSSGUsers(int arpNum) const; void setPitchSSGType(int ptNum, SequenceType type); void addPitchSSGSequenceCommand(int ptNum, int type, int data); void removePitchSSGSequenceCommand(int ptNum); void setPitchSSGSequenceCommand(int ptNum, int cnt, int type, int data); void setPitchSSGLoops(int ptNum, std::vector begins, std::vector ends, std::vector times); void setPitchSSGRelease(int ptNum, ReleaseType type, int begin); void setInstrumentSSGPitch(int instNum, int ptNum); void setInstrumentSSGPitchEnabled(int instNum, bool enabled); std::vector getPitchSSGUsers(int ptNum) const; // Song edit void setCurrentSongNumber(int num); int getCurrentSongNumber() const; // Order edit int getCurrentOrderNumber() const; void setCurrentOrderNumber(int num); // Pattern edit int getCurrentStepNumber() const; void setCurrentStepNumber(int num); // Undo-Redo void undo(); void redo(); void clearCommandHistory(); // Jam mode void toggleJamMode(); bool isJamMode() const; void jamKeyOn(JamKey key); void jamKeyOn(int keyNum); void jamKeyOff(JamKey key); void jamKeyOff(int keyNum); void jamKeyOnForced(JamKey key, SoundSource src); void jamKeyOnForced(int keyNum, SoundSource src); void jamKeyOffForced(JamKey key, SoundSource src); void jamKeyOffForced(int keyNum, SoundSource src); // Play song void startPlaySong(); void startPlayFromStart(); void startPlayPattern(); void startPlayFromCurrentStep(); void stopPlaySong(); bool isPlaySong() const; PlaybackState getPlaybackState() const; void setTrackMuteState(int trackNum, bool isMute); bool isMute(int trackNum); void setFollowPlay(bool isFollowed); bool isFollowPlay() const; int getPlayingOrderNumber() const; int getPlayingStepNumber() const; // Export bool exportToWav(BinaryContainer& container, int rate, int loopCnt, std::function bar); bool exportToVgm(BinaryContainer& container, int target, bool gd3TagEnabled, GD3Tag tag, std::function bar); bool exportToS98(BinaryContainer& container, int target, bool tagEnabled, S98Tag tag, int rate, std::function bar); // Real chip interface void useSCCI(scci::SoundInterfaceManager* manager); void useC86CTL(C86ctlBase* base); RealChipInterface getRealChipinterface() const; // Stream events /// 0<: Tick /// 0: Step /// -1: Stop int streamCountUp(); void getStreamSamples(int16_t *container, size_t nSamples); void killSound(); // Stream details int getStreamRate() const; void setStreamRate(int rate); int getStreamDuration() const; void setStreamDuration(int duration); int getStreamTempo() const; int getStreamSpeed() const; bool getStreamGrooveEnabled() const; void setMasterVolume(int percentage); void setMasterVolumeFM(double dB); void setMasterVolumeSSG(double dB); // Module details /*----- Module -----*/ void makeNewModule(); void loadModule(BinaryContainer& container); void saveModule(BinaryContainer& container); void setModulePath(std::string path); std::string getModulePath() const; void setModuleTitle(std::string title); std::string getModuleTitle() const; void setModuleAuthor(std::string author); std::string getModuleAuthor() const; void setModuleCopyright(std::string copyright); std::string getModuleCopyright() const; void setModuleComment(std::string comment); std::string getModuleComment() const; void setModuleTickFrequency(unsigned int freq); unsigned int getModuleTickFrequency() const; void setModuleStepHighlight1Distance(size_t dist); size_t getModuleStepHighlight1Distance() const; void setModuleStepHighlight2Distance(size_t dist); size_t getModuleStepHighlight2Distance() const; void setModuleMixerType(MixerType type); MixerType getModuleMixerType() const; void setModuleCustomMixerFMLevel(double level); double getModuleCustomMixerFMLevel() const; void setModuleCustomMixerSSGLevel(double level); double getModuleCustomMixerSSGLevel() const; size_t getGrooveCount() const; void setGroove(int num, std::vector seq); void setGrooves(std::vector> seqs); std::vector getGroove(int num) const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(std::vector> list); /*----- Song -----*/ void setSongTitle(int songNum, std::string title); std::string getSongTitle(int songNum) const; void setSongTempo(int songNum, int tempo); int getSongTempo(int songNum) const; void setSongGroove(int songNum, int groove); int getSongGroove(int songNum) const; void toggleTempoOrGrooveInSong(int songNum, bool isTempo); bool isUsedTempoInSong(int songNum) const; SongStyle getSongStyle(int songNum) const; void setSongSpeed(int songNum, int speed); int getSongSpeed(int songNum) const; size_t getSongCount() const; void addSong(SongType songType, std::string title); void sortSongs(std::vector numbers); size_t getAllStepCount(int songNum, int loopCnt) const; /*----- Track -----*/ void setEffectDisplayWidth(int songNum, int trackNum, size_t w); size_t getEffectDisplayWidth(int songNum, int trackNum) const; /*----- Order -----*/ std::vector getOrderData(int songNum, int orderNum) const; void setOrderPatternDigit(int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry); void insertOrderBelow(int songNum, int orderNum); void deleteOrder(int songNum, int orderNum); void pasteOrderCells(int songNum, int beginTrack, int beginOrder, std::vector> cells); void duplicateOrder(int songNum, int orderNum); void MoveOrder(int songNum, int orderNum, bool isUp); void clonePatterns(int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack); void cloneOrder(int songNum, int orderNum); size_t getOrderSize(int songNum) const; bool canAddNewOrder(int songNum) const; /*----- Pattern -----*/ int getStepNoteNumber(int songNum, int trackNum, int orderNum, int stepNum) const; void setStepNote(int songNum, int trackNum, int orderNum, int stepNum, int octave, Note note, bool autosetInst); void setStepKeyOff(int songNum, int trackNum, int orderNum, int stepNum); void setEchoBufferAccess(int songNum, int trackNum, int orderNum, int stepNum, int bufNum); void eraseStepNote(int songNum, int trackNum, int orderNum, int stepNum); int getStepInstrument(int songNum, int trackNum, int orderNum, int stepNum) const; void setStepInstrumentDigit(int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry); void eraseStepInstrument(int songNum, int trackNum, int orderNum, int stepNum); int getStepVolume(int songNum, int trackNum, int orderNum, int stepNum) const; void setStepVolumeDigit(int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry); void eraseStepVolume(int songNum, int trackNum, int orderNum, int stepNum); std::string getStepEffectID(int songNum, int trackNum, int orderNum, int stepNum, int n) const; void setStepEffectIDCharacter(int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry); int getStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n) const; void setStepEffectValueDigit(int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry); void eraseStepEffect(int songNum, int trackNum, int orderNum, int stepNum, int n); void eraseStepEffectValue(int songNum, int trackNum, int orderNum, int stepNum, int n); void deletePreviousStep(int songNum, int trackNum, int orderNum, int stepNum); void insertStep(int songNum, int trackNum, int orderNum, int stepNum); /// beginColumn /// 0: note /// 1: instrument /// 2: volume /// 3: effect id /// 4: effect value void pastePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void pasteMixPatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void pasteOverwritePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); std::vector> arrangePatternDataCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void erasePatternCells(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void increaseNoteKeyInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void decreaseNoteKeyInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void increaseNoteOctaveInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void decreaseNoteOctaveInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void expandPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void shrinkPattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void interpolatePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void reversePattern(int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColmn, int endStep); void replaceInstrumentInPattern(int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInstNum); size_t getPatternSizeFromOrderNumber(int songNum, int orderNum) const; void setDefaultPatternSize(int songNum, size_t size); size_t getDefaultPatternSize(int songNum) const; /*----- Visual -----*/ void getOutputHistory(int16_t* container); private: CommandManager comMan_; std::shared_ptr instMan_; std::unique_ptr jamMan_; std::shared_ptr opnaCtrl_; std::shared_ptr tickCounter_; std::unique_ptr playback_; std::shared_ptr mod_; // Current status int octave_; // 0-7 int curSongNum_; SongStyle songStyle_; int curTrackNum_; int curOrderNum_, curStepNum_; /// -1: not set int curInstNum_; std::vector muteStateFM_, muteStateSSG_, muteStateDrum_; bool isFollowPlay_; static const uint32_t CHIP_CLOCK; // Jam mode void funcJamKeyOn(JamKey key, int keyNum, const TrackAttribute& attrib); void funcJamKeyOff(JamKey key, int keyNum, const TrackAttribute& attrib); // Play song void startPlay(); void checkNextPositionOfLastStep(int& endOrder, int& endStep) const; }; BambooTracker-0.3.5/BambooTracker/bamboo_tracker.qrc000066400000000000000000000043261362177441300224430ustar00rootroot00000000000000 res/icon/if_arrow_redo_4989.png res/icon/if_arrow_undo_4998.png res/icon/if_cog_5175.png res/icon/if_cut_red_5249.png res/icon/if_disk_5276.png res/icon/if_folder_5362.png res/icon/if_page_white_5568.png res/icon/if_page_white_copy_5579.png res/icon/if_paste_plain_5629.png res/icon/if_resultset_last_5687.png res/icon/if_resultset_next_5688.png res/icon/if_shape_square_5750.png res/icon/if_table_edit_5800.png res/icon/inst_ssg.png res/icon/inst_fm.png res/icon/BambooTracker.ico res/icon/iconfinder_add_4941.png res/icon/iconfinder_cross_5233.png res/icon/iconfinder_page_copy_5551.png res/icon/iconfinder_page_white_get_5593.png res/icon/iconfinder_page_white_put_5609.png res/icon/iconfinder_pencil_5631.png res/icon/iconfinder_stop_5785.png res/icon/iconfinder_arrow_down_4982.png res/icon/iconfinder_arrow_up_4999.png res/icon/iconfinder_asterisk_yellow_5001.png res/icon/iconfinder_page_add_5548.png res/icon/iconfinder_page_delete_5552.png res/icon/iconfinder_textfield_rename_5877.png res/doc/keyboard_shortcuts.html BambooTracker-0.3.5/BambooTracker/chips/000077500000000000000000000000001362177441300200635ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/chips/c86ctl/000077500000000000000000000000001362177441300211665ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/chips/c86ctl/c86ctl.h000066400000000000000000000100571362177441300224450ustar00rootroot00000000000000/*** c86ctl Copyright (c) 2009-2012, honet. All rights reserved. This software is licensed under the BSD license. honet.kk(at)gmail.com */ #ifdef _WIN32 #ifndef _C86CTL_H #define _C86CTL_H #include #ifdef __cplusplus namespace c86ctl{ #endif /*----------------------------------------------------------------------------*/ /* 定数定義 */ /*----------------------------------------------------------------------------*/ #define C86CTL_ERR_NONE 0 #define C86CTL_ERR_UNKNOWN -1 #define C86CTL_ERR_INVALID_PARAM -2 #define C86CTL_ERR_UNSUPPORTED -3 #define C86CTL_ERR_NODEVICE -1000 #define C86CTL_ERR_NOT_IMPLEMENTED -9999 enum ChipType { CHIP_UNKNOWN = 0, CHIP_OPNA, CHIP_OPM, CHIP_OPN3L, CHIP_OPL3 }; /*----------------------------------------------------------------------------*/ /* 構造体定義 */ /*----------------------------------------------------------------------------*/ struct Devinfo{ char Devname[16]; char Rev; char Serial[15]; }; /*----------------------------------------------------------------------------*/ /* Interface定義 */ /*----------------------------------------------------------------------------*/ // IRealChipBase {5C457918-E66D-4AC1-8CB5-B91C4704DF79} static const GUID IID_IRealChipBase = { 0x5c457918, 0xe66d, 0x4ac1, { 0x8c, 0xb5, 0xb9, 0x1c, 0x47, 0x4, 0xdf, 0x79 } }; interface IRealChipBase : public IUnknown { virtual int __stdcall initialize(void) = 0; virtual int __stdcall deinitialize(void) = 0; virtual int __stdcall getNumberOfChip(void) = 0; virtual HRESULT __stdcall getChipInterface( int id, REFIID riid, void** ppi ) = 0; }; // IRealChip {F959C007-6B4D-46F3-BB60-9B0897C7E642} static const GUID IID_IRealChip = { 0xf959c007, 0x6b4d, 0x46f3, { 0xbb, 0x60, 0x9b, 0x8, 0x97, 0xc7, 0xe6, 0x42 } }; interface IRealChip : public IUnknown { public: virtual int __stdcall reset(void) = 0; virtual void __stdcall out( UINT addr, UCHAR data ) = 0; virtual UCHAR __stdcall in( UINT addr ) = 0; }; // IRealChip2 {BEFA830A-0DF3-46E4-A79E-FABB78E80357} static const GUID IID_IRealChip2 = { 0xbefa830a, 0xdf3, 0x46e4, { 0xa7, 0x9e, 0xfa, 0xbb, 0x78, 0xe8, 0x3, 0x57 } }; interface IRealChip2 : public IRealChip { virtual int __stdcall getChipStatus( UINT addr, UCHAR *status ) = 0; virtual void __stdcall directOut(UINT addr, UCHAR data) = 0; }; // IGimic {175C7DA0-8AA5-4173-96DA-BB43B8EB8F17} static const GUID IID_IGimic = { 0x175c7da0, 0x8aa5, 0x4173, { 0x96, 0xda, 0xbb, 0x43, 0xb8, 0xeb, 0x8f, 0x17 } }; interface IGimic : public IUnknown { virtual int __stdcall getFWVer( UINT *major, UINT *minor, UINT *revision, UINT *build ) = 0; virtual int __stdcall getMBInfo( struct Devinfo *info ) = 0; virtual int __stdcall getModuleInfo( struct Devinfo *info ) = 0; virtual int __stdcall setSSGVolume(UCHAR vol) = 0; virtual int __stdcall getSSGVolume(UCHAR *vol) = 0; virtual int __stdcall setPLLClock(UINT clock) = 0; virtual int __stdcall getPLLClock(UINT *clock) = 0; }; // IGimic2 {47141A01-15F5-4BF5-9554-CA7AACD54BB8} static const GUID IID_IGimic2 = { 0x47141a01, 0x15f5, 0x4bf5, { 0x95, 0x54, 0xca, 0x7a, 0xac, 0xd5, 0x4b, 0xb8 } }; interface IGimic2 : public IGimic { virtual int __stdcall getModuleType( enum ChipType *type ) = 0; }; /*----------------------------------------------------------------------------*/ /* 公開関数定義 */ /*----------------------------------------------------------------------------*/ #ifdef __cplusplus extern "C" { #endif HRESULT WINAPI CreateInstance( REFIID riid, void** ppi ); int WINAPI c86ctl_initialize(void); // DEPRECATED int WINAPI c86ctl_deinitialize(void); // DEPRECATED int WINAPI c86ctl_reset(void); // DEPRECATED void WINAPI c86ctl_out( UINT addr, UCHAR data ); // DEPRECATED UCHAR WINAPI c86ctl_in( UINT addr ); // DEPRECATED #ifdef __cplusplus }; }; #endif #endif #endif BambooTracker-0.3.5/BambooTracker/chips/c86ctl/c86ctl_wrapper.cpp000066400000000000000000000034761362177441300245470ustar00rootroot00000000000000#include "c86ctl_wrapper.hpp" #include "c86ctl.h" C86ctlBase::C86ctlBase(void (*func)()) : base_(nullptr) { #ifdef _C86CTL_H if (auto createInstance = reinterpret_cast(func)) createInstance(c86ctl::IID_IRealChipBase, reinterpret_cast(&base_)); #endif } bool C86ctlBase::isEmpty() const { return (base_ == nullptr); } void C86ctlBase::initialize() { #ifdef _C86CTL_H if (base_) base_->initialize(); #endif } void C86ctlBase::deinitialize() { #ifdef _C86CTL_H if (base_) base_->deinitialize(); #endif } int C86ctlBase::getNumberOfChip() { #ifdef _C86CTL_H if (base_) return base_->getNumberOfChip(); #endif return 0; } C86ctlRealChip* C86ctlBase::getChipInterface(int id) { c86ctl::IRealChip2* rc = nullptr; #ifdef _C86CTL_H if (base_) base_->getChipInterface(id, c86ctl::IID_IRealChip2, reinterpret_cast(&rc)); #endif return (rc ? new C86ctlRealChip(rc) : nullptr); } C86ctlRealChip::C86ctlRealChip(c86ctl::IRealChip2* rc) : rc_(rc) {} C86ctlRealChip::~C86ctlRealChip() { #ifdef _C86CTL_H rc_->Release(); #endif } void C86ctlRealChip::resetChip() { #ifdef _C86CTL_H rc_->reset(); #endif } void C86ctlRealChip::out(uint32_t addr, uint8_t data) { #ifdef _C86CTL_H rc_->out(addr, data); #endif } C86ctlGimic* C86ctlRealChip::queryInterface() { #ifdef _C86CTL_H c86ctl::IGimic2* gm = nullptr; if (rc_->QueryInterface(c86ctl::IID_IGimic2, reinterpret_cast(&gm)) == S_OK) { c86ctl::ChipType type; gm->getModuleType(&type); if (type == c86ctl::CHIP_OPNA) { return new C86ctlGimic(gm); } gm->Release(); } #endif return nullptr; } C86ctlGimic::C86ctlGimic(c86ctl::IGimic2* gm) : gm_(gm) {} C86ctlGimic::~C86ctlGimic() { #ifdef _C86CTL_H gm_->Release(); #endif } void C86ctlGimic::setSSGVolume(uint8_t vol) { #ifdef _C86CTL_H gm_->setSSGVolume(vol); #endif } BambooTracker-0.3.5/BambooTracker/chips/c86ctl/c86ctl_wrapper.hpp000066400000000000000000000014321362177441300245420ustar00rootroot00000000000000#pragma once #include namespace c86ctl { struct IRealChipBase; struct IRealChip2; struct IGimic2; } class C86ctlRealChip; class C86ctlGimic; class C86ctlBase { public: explicit C86ctlBase(void (*func)()); bool isEmpty() const; void initialize(); void deinitialize(); int getNumberOfChip(); C86ctlRealChip* getChipInterface(int id); private: c86ctl::IRealChipBase* base_; }; class C86ctlRealChip { public: explicit C86ctlRealChip(c86ctl::IRealChip2* rc); ~C86ctlRealChip(); void resetChip(); void out(uint32_t addr, uint8_t data); C86ctlGimic* queryInterface(); private: c86ctl::IRealChip2* rc_; }; class C86ctlGimic { public: explicit C86ctlGimic(c86ctl::IGimic2* gm); ~C86ctlGimic(); void setSSGVolume(uint8_t vol); private: c86ctl::IGimic2* gm_; }; BambooTracker-0.3.5/BambooTracker/chips/chip.cpp000066400000000000000000000041711362177441300215150ustar00rootroot00000000000000#include "chip.hpp" #include #include "chip_misc.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus #include "mame/mamedef.h" UINT8 CHIP_SAMPLING_MODE = 0x00; INT32 CHIP_SAMPLE_RATE; stream_sample_t* DUMMYBUF[] = { nullptr, nullptr }; #ifdef __cplusplus } #endif // __cplusplus namespace chip { Chip::Chip(int id, int clock, int rate, int autoRate, size_t maxDuration, std::unique_ptr resampler1, std::unique_ptr resampler2, std::shared_ptr exportContainer) : id_(id), rate_(rate), // Dummy set clock_(clock), autoRate_(autoRate), maxDuration_(maxDuration), masterVolumeRatio_(100), exCntr_(exportContainer), needSampleGen_(isNeedSampleGeneration(exportContainer)) { resampler_[0] = std::move(resampler1); resampler_[1] = std::move(resampler2); for (int pan = LEFT; pan <= RIGHT; ++pan) { for (auto& buf : buffer_) { buf[pan] = new stream_sample_t[SMPL_BUF_SIZE_]; } } } Chip::~Chip() { for (int pan = LEFT; pan <= RIGHT; ++pan) { for (auto& buf : buffer_) { delete[] buf[pan]; } } } void Chip::initResampler() { for (int snd = 0; snd < 2; ++snd) { resampler_[snd]->init(internalRate_[snd], rate_, maxDuration_); } } void Chip::setRate(int rate) { std::lock_guard lg(mutex_); funcSetRate(rate); for (auto& rsmp : resampler_) { rsmp->setDestributionRate(rate); } } void Chip::funcSetRate(int rate) { rate_ = CHIP_SAMPLE_RATE = ((rate) ? rate : autoRate_); } int Chip::getClock() const { return clock_; } int Chip::getRate() const { return rate_; } void Chip::setMaxDuration(size_t maxDuration) { maxDuration_ = maxDuration; for (int snd = 0; snd < 2; ++snd) { resampler_[snd]->setMaxDuration(maxDuration); } } size_t Chip::getMaxDuration() const { return maxDuration_; } void Chip::setExportContainer(std::shared_ptr cntr) { exCntr_ = cntr; needSampleGen_ = isNeedSampleGeneration(cntr); } void Chip::setMasterVolume(int percentage) { masterVolumeRatio_ = percentage / 100.0; } } BambooTracker-0.3.5/BambooTracker/chips/chip.hpp000066400000000000000000000030651362177441300215230ustar00rootroot00000000000000#pragma once #include "chip_def.h" #include #include #include #include "resampler.hpp" #include "export_container.hpp" namespace chip { class Chip { public: // [rate] // 0 = auto-set mode (set internal chip rate) Chip(int id, int clock, int rate, int autoRate, size_t maxDuration, std::unique_ptr resampler1, std::unique_ptr resampler2, std::shared_ptr exportContainer); virtual ~Chip(); virtual void reset() = 0; virtual void setRegister(uint32_t offset, uint8_t value) = 0; virtual uint8_t getRegister(uint32_t offset) const = 0; virtual void setRate(int rate); int getRate() const; int getClock() const; void setMaxDuration(size_t maxDuration); size_t getMaxDuration() const; void setExportContainer(std::shared_ptr cntr = nullptr); void setMasterVolume(int percentage); virtual void mix(int16_t* stream, size_t nSamples) = 0; protected: const int id_; std::mutex mutex_; int rate_, clock_; const int autoRate_; int internalRate_[2]; size_t maxDuration_; double masterVolumeRatio_; double volumeRatio_[2]; sample* buffer_[2][2]; std::unique_ptr resampler_[2]; std::shared_ptr exCntr_; bool needSampleGen_; void initResampler(); void funcSetRate(int rate); private: inline static bool isNeedSampleGeneration(std::shared_ptr cntr) { return (cntr == nullptr || cntr->isNeedSampleGeneration()); } }; } BambooTracker-0.3.5/BambooTracker/chips/chip_def.h000066400000000000000000000037031362177441300220000ustar00rootroot00000000000000#pragma once #include typedef int32_t sample; struct intf2608 { void (*set_ay_emu_core)(uint8_t Emulator); int (*device_start)(uint8_t ChipID, int clock, uint8_t AYDisable, uint8_t AYFlags, int* AYrate); void (*device_stop)(uint8_t ChipID); void (*device_reset)(uint8_t ChipID); void (*control_port_a_w)(uint8_t ChipID, uint32_t offset, uint8_t data); void (*control_port_b_w)(uint8_t ChipID, uint32_t offset, uint8_t data); void (*data_port_a_w)(uint8_t ChipID, uint32_t offset, uint8_t data); void (*data_port_b_w)(uint8_t ChipID, uint32_t offset, uint8_t data); uint8_t (*read_port_r)(uint8_t ChipID, uint32_t offset); void (*stream_update)(uint8_t ChipID, sample **outputs, int samples); void (*stream_update_ay)(uint8_t ChipID, sample **outputs, int samples); }; #ifndef INCLUDE_AY8910_H // Copied from VGMPlay/chips/ay8910.h /* * Default values for resistor loads. * The macro should be used in AY8910interface if * the real values are unknown. */ #define AY8910_DEFAULT_LOADS {1000, 1000, 1000} /* * The following is used by all drivers not reviewed yet. * This will like the old behaviour, output between * 0 and 7FFF */ #define AY8910_LEGACY_OUTPUT (1) /* * Specifing the next define will simulate the special * cross channel mixing if outputs are tied together. * The driver will only provide one stream in this case. */ #define AY8910_SINGLE_OUTPUT (2) typedef struct _ay8910_interface ay8910_interface; struct _ay8910_interface { int flags; /* Flags */ int res_load[3]; /* Load on channel in ohms */ //devcb_read8 portAread; //devcb_read8 portBread; //devcb_write8 portAwrite; //devcb_write8 portBwrite; }; #endif typedef struct _ym2608_interface ym2608_interface; struct _ym2608_interface { ay8910_interface ay8910_intf; //void ( *handler )( const device_config *device, int irq ); /* IRQ handler for the YM2608 */ void ( *handler )( int irq ); /* IRQ handler for the YM2608 */ }; BambooTracker-0.3.5/BambooTracker/chips/chip_misc.h000066400000000000000000000005771362177441300222030ustar00rootroot00000000000000#pragma once #include namespace chip { enum class Emu { Mame, Nuked, First = Mame, Last = Nuked, }; const size_t SMPL_BUF_SIZE_ = 0x10000; enum Stereo : int { LEFT = 0, RIGHT = 1, }; template inline const T& clamp(const T& value, const T& low, const T& high) { return std::min(std::max(value, low), high); } } BambooTracker-0.3.5/BambooTracker/chips/export_container.cpp000066400000000000000000000220321362177441300241510ustar00rootroot00000000000000#include "export_container.hpp" #include "export_handler.hpp" #include namespace chip { ExportContainerInterface::~ExportContainerInterface() {} //******************************// WavExportContainer::WavExportContainer() { } void WavExportContainer::recordRegisterChange(uint32_t offset, uint8_t value) { (void)offset; (void)value; } void WavExportContainer::recordStream(int16_t* stream, size_t nSamples) { std::copy(stream, stream + (nSamples << 1), std::back_inserter(samples_)); } bool WavExportContainer::empty() const { return samples_.empty(); } void WavExportContainer::clear() { samples_.clear(); } std::vector WavExportContainer::getStream() const { return samples_; } //******************************// VgmExportContainer::VgmExportContainer(int target, uint32_t intrRate) : target_(target), lastWait_(0), totalSampCnt_(0), intrRate_(intrRate), isSetLoop_(false), loopPoint_(0) { } void VgmExportContainer::recordRegisterChange(uint32_t offset, uint8_t value) { if (lastWait_) setWait(); const int fm = target_ & Export_FmMask; const int ssg = target_ & Export_SsgMask; const uint8_t cmdSsg = (ssg != Export_InternalSsg) ? 0xa0 : (fm == Export_YM2608) ? 0x56 : (fm == Export_YM2203) ? 0x55 : 0x00; const uint8_t cmdFmPortA = (fm == Export_YM2608) ? 0x56 : (fm == Export_YM2612) ? 0x52 : (fm == Export_YM2203) ? 0x55 : 0x00; const uint8_t cmdFmPortB = (fm == Export_YM2608) ? 0x57 : (fm == Export_YM2612) ? 0x53 : 0x00; if (cmdSsg && offset < 0x10) { buf_.push_back(cmdSsg); buf_.push_back(offset); buf_.push_back(value); } else if (cmdFmPortA && (offset & 0x100) == 0) { bool compatible = true; if (offset == 0x28) { // Key register if (fm == Export_YM2203 && (value & 7) >= 3) compatible = false; } else if (offset == 0x29) // Mode register compatible = fm == Export_YM2608; else if ((offset & 0xf0) == 0x10) // Rhythm section compatible = fm == Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortA); buf_.push_back(offset & 0xff); buf_.push_back(value); } } else if (cmdFmPortB && (offset & 0x100) != 0) { bool compatible = true; if (offset < 0x10) // ADPCM section compatible = fm == Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortB); buf_.push_back(offset & 0xff); buf_.push_back(value); } } } void VgmExportContainer::recordStream(int16_t* stream, size_t nSamples) { (void)stream; lastWait_ += nSamples; totalSampCnt_ += nSamples; } void VgmExportContainer::clear() { buf_.clear(); lastWait_ = 0; totalSampCnt_ = 0; isSetLoop_ = false; loopPoint_ = 0; } bool VgmExportContainer::empty() const { return (buf_.empty() || lastWait_ != 0); } std::vector VgmExportContainer::getData() { if (lastWait_) setWait(); return buf_; } size_t VgmExportContainer::getSampleLength() const { return totalSampCnt_; } size_t VgmExportContainer::setLoopPoint() { if (lastWait_) setWait(); isSetLoop_ = true; return loopPoint_; } size_t VgmExportContainer::forceMoveLoopPoint() { loopPoint_ = buf_.size(); return loopPoint_; } void VgmExportContainer::setWait() { while (lastWait_) { uint32_t sub; if (intrRate_ == 50) { if (lastWait_ > 65535) { uint32_t tmp = lastWait_ - 65535; if (tmp <= 882) { //65535 - (882 - tmp) sub = 64653 + tmp; } else if (tmp <= 1764) { //65535 - (1764 - tmp) sub = 63771 + tmp; } else if (tmp <= 2646) { //65535 - (2646 - tmp) sub = 62889 + tmp; } else { sub = 65535; } buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(sub >> 8); } else { if (lastWait_ <= 16) { buf_.push_back(0x70 | (lastWait_ - 1)); } else if (lastWait_ > 2646) { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(lastWait_ >> 8); } else if (lastWait_ == 2646) { buf_.push_back(0x63); buf_.push_back(0x63); buf_.push_back(0x63); } else if (1764 <= lastWait_ && lastWait_ <= 1780) { uint32_t tmp = lastWait_ - 1764; buf_.push_back(0x63); buf_.push_back(0x63); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else if (882 <= lastWait_ && lastWait_ <= 898) { uint32_t tmp = lastWait_ - 882; buf_.push_back(0x63); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(lastWait_ >> 8); } sub = lastWait_; } } else if (intrRate_ == 60) { if (lastWait_ > 65535) { uint32_t tmp = lastWait_ - 65535; if (tmp <= 735) { //65535 - (735 - tmp) sub = 64800 + tmp; } else if (tmp <= 1470) { //65535 - (1470 - tmp) sub = 64065 + tmp; } else if (tmp <= 2205) { //65535 - (2205 - tmp) sub = 63330 + tmp; } else { sub = 65535; } buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(sub >> 8); } else { if (lastWait_ <= 16) { buf_.push_back(0x70 | (lastWait_ - 1)); } else if (lastWait_ > 2205) { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(lastWait_ >> 8); } else if (lastWait_ == 2205) { buf_.push_back(0x62); buf_.push_back(0x62); buf_.push_back(0x62); } else if (1470 <= lastWait_ && lastWait_ <= 1486) { uint32_t tmp = lastWait_ - 1470; buf_.push_back(0x62); buf_.push_back(0x62); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else if (735 <= lastWait_ && lastWait_ <= 751) { uint32_t tmp = lastWait_ - 735; buf_.push_back(0x62); if (tmp) buf_.push_back(0x70 | (tmp - 1)); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(lastWait_ >> 8); } sub = lastWait_; } } else { if (lastWait_ > 65535) { sub = 65535; buf_.push_back(0x61); buf_.push_back(sub & 0x00ff); buf_.push_back(sub >> 8); } else { buf_.push_back(0x61); buf_.push_back(lastWait_ & 0x00ff); buf_.push_back(lastWait_ >> 8); } sub = lastWait_; } lastWait_ -= sub; } if (!isSetLoop_) loopPoint_ = buf_.size(); } //******************************// S98ExportContainer::S98ExportContainer(int target) : target_(target), lastWait_(0), totalSampCnt_(0), isSetLoop_(false), loopPoint_(0) { } void S98ExportContainer::recordRegisterChange(uint32_t offset, uint8_t value) { if (lastWait_) setWait(); const int fm = target_ & Export_FmMask; const int ssg = target_ & Export_SsgMask; const uint8_t cmdSsg = (ssg != Export_InternalSsg) ? 0x02 : (fm == Export_YM2608) ? 0x00 : (fm == Export_YM2203) ? 0x00 : 0xff; const uint8_t cmdFmPortA = 0x00; const uint8_t cmdFmPortB = (fm == Export_YM2608 || fm == Export_YM2612) ? 0x01 : 0xff; if (cmdSsg != 0xff && offset < 0x10) { buf_.push_back(cmdSsg); buf_.push_back(offset); buf_.push_back(value); } else if (cmdFmPortA != 0xff && (offset & 0x100) == 0) { bool compatible = true; if (offset == 0x28) { // Key register if (fm == Export_YM2203 && (value & 7) >= 3) compatible = false; } else if (offset == 0x29) // Mode register compatible = fm == Export_YM2608; else if ((offset & 0xf0) == 0x10) // Rhythm section compatible = fm == Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortA); buf_.push_back(offset & 0xff); buf_.push_back(value); } } else if (cmdFmPortB != 0xff && (offset & 0x100) != 0) { bool compatible = true; if (offset < 0x10) // ADPCM section compatible = fm == Export_YM2608; if (compatible) { buf_.push_back(cmdFmPortB); buf_.push_back(offset & 0xff); buf_.push_back(value); } } } void S98ExportContainer::recordStream(int16_t* stream, size_t nSamples) { (void)stream; lastWait_ += nSamples; totalSampCnt_ += nSamples; } void S98ExportContainer::clear() { buf_.clear(); lastWait_ = 0; totalSampCnt_ = 0; isSetLoop_ = false; loopPoint_ = 0; } bool S98ExportContainer::empty() const { return (buf_.empty() || lastWait_ != 0); } std::vector S98ExportContainer::getData() { if (lastWait_) setWait(); return buf_; } size_t S98ExportContainer::getSampleLength() const { return totalSampCnt_; } size_t S98ExportContainer::setLoopPoint() { if (lastWait_) setWait(); isSetLoop_ = true; return loopPoint_; } size_t S98ExportContainer::forceMoveLoopPoint() { loopPoint_ = buf_.size(); return loopPoint_; } void S98ExportContainer::setWait() { if (lastWait_ == 1) { buf_.push_back(0xff); } else { buf_.push_back(0xfe); lastWait_ -= 2; do { uint8_t b = lastWait_ & 0x7f; lastWait_ >>= 7; if (lastWait_ > 0) b |= 0x80; buf_.push_back(b); } while (lastWait_ > 0); } if (!isSetLoop_) loopPoint_ = buf_.size(); lastWait_ = 0; } } BambooTracker-0.3.5/BambooTracker/chips/export_container.hpp000066400000000000000000000042371362177441300241650ustar00rootroot00000000000000#pragma once #include #include #include namespace chip { class ExportContainerInterface { public: virtual ~ExportContainerInterface(); virtual bool isNeedSampleGeneration() const = 0; virtual void recordRegisterChange(uint32_t offset, uint8_t value) = 0; virtual void recordStream(int16_t* stream, size_t nSamples) = 0; virtual bool empty() const = 0; virtual void clear() = 0; }; class WavExportContainer : public ExportContainerInterface { public: WavExportContainer(); bool isNeedSampleGeneration() const override { return true; } void recordRegisterChange(uint32_t offset, uint8_t value) override; void recordStream(int16_t* stream, size_t nSamples) override; bool empty() const override; void clear() override; std::vector getStream() const; private: std::vector samples_; }; class VgmExportContainer : public ExportContainerInterface { public: VgmExportContainer(int target, uint32_t intrRate); bool isNeedSampleGeneration() const override { return false; } void recordRegisterChange(uint32_t offset, uint8_t value) override; void recordStream(int16_t* stream, size_t nSamples) override; void clear() override; bool empty() const override; std::vector getData(); size_t getSampleLength() const; size_t setLoopPoint(); size_t forceMoveLoopPoint(); private: std::vector buf_; int target_; uint64_t lastWait_, totalSampCnt_; uint32_t intrRate_; bool isSetLoop_; uint32_t loopPoint_; void setWait(); }; class S98ExportContainer : public ExportContainerInterface { public: explicit S98ExportContainer(int target); bool isNeedSampleGeneration() const override { return false; } void recordRegisterChange(uint32_t offset, uint8_t value) override; void recordStream(int16_t* stream, size_t nSamples) override; void clear() override; bool empty() const override; std::vector getData(); size_t getSampleLength() const; size_t setLoopPoint(); size_t forceMoveLoopPoint(); private: std::vector buf_; int target_; uint64_t lastWait_, totalSampCnt_; bool isSetLoop_; uint32_t loopPoint_; void setWait(); }; } BambooTracker-0.3.5/BambooTracker/chips/mame/000077500000000000000000000000001362177441300210025ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/chips/mame/2608intf.c000066400000000000000000000310301362177441300224230ustar00rootroot00000000000000/*************************************************************************** 2608intf.c The YM2608 emulator supports up to 2 chips. Each chip has the following connections: - Status Read / Control Write A - Port Read / Data Write A - Control Write B - Data Write B ***************************************************************************/ #include // for free #include // for memset #include // for NULL //#include "mamedef.h" //#include "sndintrf.h" //#include "streams.h" #include "2608intf.h" //#include "fm.h" // Only use EC_EMU2149 //#define ENABLE_ALL_CORES #ifdef ENABLE_ALL_CORES #define EC_MAME 0x01 // AY8910 core from MAME #endif #define EC_EMU2149 0x00 typedef struct _ym2608_state ym2608_state; struct _ym2608_state { //sound_stream * stream; //emu_timer * timer[2]; void * chip; void * psg; ym2608_interface intf; //const device_config *device; }; #define CHTYPE_YM2608 0x21 extern UINT8 CHIP_SAMPLING_MODE; extern INT32 CHIP_SAMPLE_RATE; static UINT8 AY_EMU_CORE = 0x00; //extern UINT32 SampleRate; #define MAX_CHIPS 0x02 static ym2608_state YM2608Data[MAX_CHIPS]; /*INLINE ym2608_state *get_safe_token(const device_config *device) { assert(device != NULL); assert(device->token != NULL); assert(device->type == SOUND); assert(sound_get_type(device) == SOUND_YM2608); return (ym2608_state *)device->token; }*/ static void psg_set_clock(void *param, int clock) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_set_clock_ym(info->psg, clock); break; #endif case EC_EMU2149: PSG_set_clock((PSG*)info->psg, clock); break; } } } static void psg_write(void *param, int address, int data) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_write_ym(info->psg, address, data); break; #endif case EC_EMU2149: PSG_writeIO((PSG*)info->psg, address, data); break; } } } static int psg_read(void *param) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: return ay8910_read_ym(info->psg); #endif case EC_EMU2149: return PSG_readIO((PSG*)info->psg); } } return 0x00; } static void psg_reset(void *param) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_reset_ym(info->psg); break; #endif case EC_EMU2149: PSG_reset((PSG*)info->psg); break; } } } static const ssg_callbacks psgintf = { psg_set_clock, psg_write, psg_read, psg_reset }; /* IRQ Handler */ /*static void IRQHandler(void *param,int irq) { ym2608_state *info = (ym2608_state *)param; //if(info->intf->handler) info->intf->handler(info->device, irq); if(info->intf.handler) info->intf.handler(irq); }*/ /* Timer overflow callback from timer.c */ /*static TIMER_CALLBACK( timer_callback_2608_0 ) { ym2608_state *info = (ym2608_state *)ptr; ym2608_timer_over(info->chip,0); } static TIMER_CALLBACK( timer_callback_2608_1 ) { ym2608_state *info = (ym2608_state *)ptr; ym2608_timer_over(info->chip,1); }*/ /*static void timer_handler(void *param,int c,int count,int clock) { ym2608_state *info = (ym2608_state *)param; if( count == 0 ) { // Reset FM Timer //timer_enable(info->timer[c], 0); } else { // Start FM Timer //attotime period = attotime_mul(ATTOTIME_IN_HZ(clock), count); //if (!timer_enable(info->timer[c], 1)) // timer_adjust_oneshot(info->timer[c], period, 0); } }*/ /* update request from fm.c */ void ym2608_update_request(void *param) { ym2608_state *info = (ym2608_state *)param; //stream_update(info->stream); ym2608_update_one(info->chip, DUMMYBUF, 0); // Not necessary. //if (info->psg != NULL) // ay8910_update_one(info->psg, DUMMYBUF, 0); } //static STREAM_UPDATE( ym2608_stream_update ) void ym2608_stream_update(UINT8 ChipID, stream_sample_t **outputs, int samples) { //ym2608_state *info = (ym2608_state *)param; ym2608_state *info = &YM2608Data[ChipID]; ym2608_update_one(info->chip, outputs, samples); } void ym2608_stream_update_ay(UINT8 ChipID, stream_sample_t **outputs, int samples) { //ym2608_state *info = (ym2608_state *)param; ym2608_state *info = &YM2608Data[ChipID]; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_update_one(info->psg, outputs, samples); break; #endif case EC_EMU2149: PSG_calc_stereo((PSG*)info->psg, outputs, samples); break; } } else { memset(outputs[0], 0x00, samples * sizeof(stream_sample_t)); memset(outputs[1], 0x00, samples * sizeof(stream_sample_t)); } } //static STATE_POSTLOAD( ym2608_intf_postload ) /*static void ym2608_intf_postload(UINT8 ChipID) { //ym2608_state *info = (ym2608_state *)param; ym2608_state *info = &YM2608Data[ChipID]; ym2608_postload(info->chip); }*/ //static DEVICE_START( ym2608 ) int device_start_ym2608(UINT8 ChipID, int clock, UINT8 AYDisable, UINT8 AYFlags, int* AYrate) { static const ym2608_interface generic_2608 = { { AY8910_LEGACY_OUTPUT | AY8910_SINGLE_OUTPUT, AY8910_DEFAULT_LOADS //DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, DEVCB_NULL }, NULL }; //const ym2608_interface *intf = device->static_config ? (const ym2608_interface *)device->static_config : &generic_2608; ym2608_interface *intf; int rate; int ay_clock; //void *pcmbufa; //int pcmsizea; //ym2608_state *info = get_safe_token(device); ym2608_state *info; if (ChipID >= MAX_CHIPS) return 0; info = &YM2608Data[ChipID]; rate = clock / 144; // FM synthesis rate is clock / 2 / 72 /*rate = clock/72;*/ if ((CHIP_SAMPLING_MODE == 0x01 && rate < CHIP_SAMPLE_RATE) || CHIP_SAMPLING_MODE == 0x02) rate = CHIP_SAMPLE_RATE; info->intf = generic_2608; intf = &info->intf; if (AYFlags) intf->ay8910_intf.flags = AYFlags; //info->device = device; /* FIXME: Force to use single output */ //info->psg = ay8910_start_ym(NULL, SOUND_YM2608, clock, &intf->ay8910_intf); if (! AYDisable) { ay_clock = clock / 4; *AYrate = ay_clock / 8; switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: info->psg = ay8910_start_ym(NULL, CHTYPE_YM2608, ay_clock, &intf->ay8910_intf); break; #endif case EC_EMU2149: info->psg = PSG_new(ay_clock, *AYrate); if (info->psg == NULL) return 0; PSG_setVolumeMode((PSG*)info->psg, 1); // YM2149 volume mode break; } } else { info->psg = NULL; *AYrate = 0; } //assert_always(info->psg != NULL, "Error creating YM2608/AY8910 chip"); /* Timer Handler set */ //info->timer[0] = timer_alloc(device->machine, timer_callback_2608_0, info); //info->timer[1] = timer_alloc(device->machine, timer_callback_2608_1, info); /* stream system initialize */ //info->stream = stream_create(device,0,2,rate,info,ym2608_stream_update); /* setup adpcm buffers */ //pcmbufa = device->region; //pcmsizea = device->regionbytes; /* initialize YM2608 */ //info->chip = ym2608_init(info,device,device->clock,rate, // pcmbufa,pcmsizea, // timer_handler,IRQHandler,&psgintf); info->chip = ym2608_init(info, clock, rate, NULL, NULL, &psgintf); //assert_always(info->chip != NULL, "Error creating YM2608 chip"); //state_save_register_postload(device->machine, ym2608_intf_postload, info); return rate; } //static DEVICE_STOP( ym2608 ) void device_stop_ym2608(UINT8 ChipID) { //ym2608_state *info = get_safe_token(device); ym2608_state *info = &YM2608Data[ChipID]; ym2608_shutdown(info->chip); if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_stop_ym(info->psg); break; #endif case EC_EMU2149: PSG_delete((PSG*)info->psg); break; } info->psg = NULL; } } //static DEVICE_RESET( ym2608 ) void device_reset_ym2608(UINT8 ChipID) { //ym2608_state *info = get_safe_token(device); ym2608_state *info = &YM2608Data[ChipID]; ym2608_reset_chip(info->chip); // also resets the AY clock //psg_reset(info); // already done as a callback in ym2608_reset_chip } //READ8_DEVICE_HANDLER( ym2608_r ) UINT8 ym2608_r(UINT8 ChipID, offs_t offset) { //ym2608_state *info = get_safe_token(device); ym2608_state *info = &YM2608Data[ChipID]; return ym2608_read(info->chip, offset & 3); } //WRITE8_DEVICE_HANDLER( ym2608_w ) void ym2608_w(UINT8 ChipID, offs_t offset, UINT8 data) { //ym2608_state *info = get_safe_token(device); ym2608_state *info = &YM2608Data[ChipID]; ym2608_write(info->chip, offset & 3, data); } //READ8_DEVICE_HANDLER( ym2608_read_port_r ) UINT8 ym2608_read_port_r(UINT8 ChipID, offs_t offset) { (void)offset; return ym2608_r(ChipID, 1); } //READ8_DEVICE_HANDLER( ym2608_status_port_a_r ) //UINT8 ym2608_status_port_a_r(UINT8 ChipID, offs_t offset) //{ // return ym2608_r(ChipID, 0); //} //READ8_DEVICE_HANDLER( ym2608_status_port_b_r ) //UINT8 ym2608_status_port_b_r(UINT8 ChipID, offs_t offset) //{ // return ym2608_r(ChipID, 2); //} //WRITE8_DEVICE_HANDLER( ym2608_control_port_a_w ) void ym2608_control_port_a_w(UINT8 ChipID, offs_t offset, UINT8 data) { (void)offset; ym2608_w(ChipID, 0, data); } //WRITE8_DEVICE_HANDLER( ym2608_control_port_b_w ) void ym2608_control_port_b_w(UINT8 ChipID, offs_t offset, UINT8 data) { (void)offset; ym2608_w(ChipID, 2, data); } //WRITE8_DEVICE_HANDLER( ym2608_data_port_a_w ) void ym2608_data_port_a_w(UINT8 ChipID, offs_t offset, UINT8 data) { (void)offset; ym2608_w(ChipID, 1, data); } //WRITE8_DEVICE_HANDLER( ym2608_data_port_b_w ) void ym2608_data_port_b_w(UINT8 ChipID, offs_t offset, UINT8 data) { (void)offset; ym2608_w(ChipID, 3, data); } void ym2608_set_ay_emu_core(UINT8 Emulator) { #ifdef ENABLE_ALL_CORES AY_EMU_CORE = (Emulator < 0x02) ? Emulator : 0x00; #else (void)Emulator; AY_EMU_CORE = EC_EMU2149; #endif return; } //void ym2608_write_data_pcmrom(UINT8 ChipID, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, // offs_t DataLength, const UINT8* ROMData) //{ // ym2608_state* info = &YM2608Data[ChipID]; // ym2608_write_pcmrom(info->chip, rom_id, ROMSize, DataStart, DataLength, ROMData); //} void ym2608_set_mute_mask(UINT8 ChipID, UINT32 MuteMaskFM, UINT32 MuteMaskAY) { ym2608_state* info = &YM2608Data[ChipID]; ym2608_set_mutemask(info->chip, MuteMaskFM); if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_set_mute_mask_ym(info->psg, MuteMaskAY); break; #endif case EC_EMU2149: PSG_setMask((PSG*)info->psg, MuteMaskAY); break; } } } //void ym2608_set_srchg_cb(UINT8 ChipID, SRATE_CALLBACK CallbackFunc, void* DataPtr, void* AYDataPtr) //{ // ym2608_state* info = &YM2608Data[ChipID]; // // if (info->psg != NULL) // { // switch(AY_EMU_CORE) // { //#ifdef ENABLE_ALL_CORES // case EC_MAME: // ay8910_set_srchg_cb_ym(info->psg, CallbackFunc, AYDataPtr); // break; //#endif // case EC_EMU2149: // break; // } // } // // return; //} /************************************************************************** * Generic get_info **************************************************************************/ /*DEVICE_GET_INFO( ym2608 ) { switch (state) { // --- the following bits of info are returned as 64-bit signed integers --- case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(ym2608_state); break; // --- the following bits of info are returned as pointers to data or functions --- case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( ym2608 ); break; case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME( ym2608 ); break; case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME( ym2608 ); break; // --- the following bits of info are returned as NULL-terminated strings --- case DEVINFO_STR_NAME: strcpy(info->s, "YM2608"); break; case DEVINFO_STR_FAMILY: strcpy(info->s, "Yamaha FM"); break; case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; } }*/ struct intf2608 mame_intf2608 = { .set_ay_emu_core = &ym2608_set_ay_emu_core, .device_start = &device_start_ym2608, .device_stop = &device_stop_ym2608, .device_reset = &device_reset_ym2608, .control_port_a_w = &ym2608_control_port_a_w, .control_port_b_w = &ym2608_control_port_b_w, .data_port_a_w = &ym2608_data_port_a_w, .data_port_b_w = &ym2608_data_port_b_w, .read_port_r = &ym2608_read_port_r, .stream_update = &ym2608_stream_update, .stream_update_ay = &ym2608_stream_update_ay, }; BambooTracker-0.3.5/BambooTracker/chips/mame/2608intf.h000066400000000000000000000036621362177441300224420ustar00rootroot00000000000000#pragma once #include "mamedef.h" #include "fm.h" #include "emu2149.h" #ifdef INCLUDE_AY8910_H #include "ay8910.h" #endif void ym2608_update_request(void *param); /*READ8_DEVICE_HANDLER( ym2608_r ); WRITE8_DEVICE_HANDLER( ym2608_w ); READ8_DEVICE_HANDLER( ym2608_read_port_r ); READ8_DEVICE_HANDLER( ym2608_status_port_a_r ); READ8_DEVICE_HANDLER( ym2608_status_port_b_r ); WRITE8_DEVICE_HANDLER( ym2608_control_port_a_w ); WRITE8_DEVICE_HANDLER( ym2608_control_port_b_w ); WRITE8_DEVICE_HANDLER( ym2608_data_port_a_w ); WRITE8_DEVICE_HANDLER( ym2608_data_port_b_w ); DEVICE_GET_INFO( ym2608 ); #define SOUND_YM2608 DEVICE_GET_INFO_NAME( ym2608 )*/ void ym2608_stream_update(UINT8 ChipID, stream_sample_t **outputs, int samples); void ym2608_stream_update_ay(UINT8 ChipID, stream_sample_t **outputs, int samples); int device_start_ym2608(UINT8 ChipID, int clock, UINT8 AYDisable, UINT8 AYFlags, int* AYrate); void device_stop_ym2608(UINT8 ChipID); void device_reset_ym2608(UINT8 ChipID); UINT8 ym2608_r(UINT8 ChipID, offs_t offset); void ym2608_w(UINT8 ChipID, offs_t offset, UINT8 data); UINT8 ym2608_read_port_r(UINT8 ChipID, offs_t offset); //UINT8 ym2608_status_port_a_r(UINT8 ChipID, offs_t offset); //UINT8 ym2608_status_port_b_r(UINT8 ChipID, offs_t offset); void ym2608_control_port_a_w(UINT8 ChipID, offs_t offset, UINT8 data); void ym2608_control_port_b_w(UINT8 ChipID, offs_t offset, UINT8 data); void ym2608_data_port_a_w(UINT8 ChipID, offs_t offset, UINT8 data); void ym2608_data_port_b_w(UINT8 ChipID, offs_t offset, UINT8 data); void ym2608_set_ay_emu_core(UINT8 Emulator); //void ym2608_write_data_pcmrom(UINT8 ChipID, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, // offs_t DataLength, const UINT8* ROMData); void ym2608_set_mute_mask(UINT8 ChipID, UINT32 MuteMaskFM, UINT32 MuteMaskAY); //void ym2608_set_srchg_cb(UINT8 ChipID, SRATE_CALLBACK CallbackFunc, void* DataPtr, void* AYDataPtr); extern struct intf2608 mame_intf2608; BambooTracker-0.3.5/BambooTracker/chips/mame/emu2149.c000066400000000000000000000254121362177441300222600ustar00rootroot00000000000000/**************************************************************************** emu2149.c -- YM2149/AY-3-8910 emulator by Mitsutaka Okazaki 2001 2001 04-28 : Version 1.00beta -- 1st Beta Release. 2001 08-14 : Version 1.10 2001 10-03 : Version 1.11 -- Added PSG_set_quality(). 2002 03-02 : Version 1.12 -- Removed PSG_init & PSG_close. 2002 10-13 : Version 1.14 -- Fixed the envelope unit. 2003 09-19 : Version 1.15 -- Added PSG_setMask and PSG_toggleMask 2004 01-11 : Version 1.16 -- Fixed an envelope problem where the envelope frequency register is written before key-on. References: psg.vhd -- 2000 written by Kazuhiro Tsujikawa. s_fme7.c -- 1999,2000 written by Mamiya (NEZplug). ay8910.c -- 1998-2001 Author unknown (MAME). MSX-Datapack -- 1991 ASCII Corp. AY-3-8910 data sheet *****************************************************************************/ #include #include #include #include "emu2149.h" static e_uint32 voltbl[2][32] = { {0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0B, 0x0D, 0x0F, 0x12, 0x16, 0x1A, 0x1F, 0x25, 0x2D, 0x35, 0x3F, 0x4C, 0x5A, 0x6A, 0x7F, 0x97, 0xB4, 0xD6, 0xEB, 0xFF}, {0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07, 0x0B, 0x0B, 0x0F, 0x0F, 0x16, 0x16, 0x1F, 0x1F, 0x2D, 0x2D, 0x3F, 0x3F, 0x5A, 0x5A, 0x7F, 0x7F, 0xB4, 0xB4, 0xFF, 0xFF} }; #define GETA_BITS 24 static void internal_refresh (PSG * psg) { if (psg->quality) { psg->base_incr = 1 << GETA_BITS; psg->realstep = (e_uint32) ((1 << 31) / psg->rate); psg->psgstep = (e_uint32) ((1 << 31) / (psg->clk / 8)); psg->psgtime = 0; } else { psg->base_incr = (e_uint32) ((double) psg->clk * (1 << GETA_BITS) / (8.0 * psg->rate)); } } EMU2149_API void PSG_set_clock(PSG * psg, e_uint32 c) { psg->clk = c; internal_refresh(psg); } EMU2149_API void PSG_set_rate (PSG * psg, e_uint32 r) { psg->rate = r ? r : 44100; internal_refresh (psg); } EMU2149_API void PSG_set_quality (PSG * psg, e_uint32 q) { psg->quality = q; internal_refresh (psg); } EMU2149_API PSG * PSG_new (e_uint32 c, e_uint32 r) { PSG *psg; psg = (PSG *) malloc (sizeof (PSG)); if (psg == NULL) return NULL; memset(psg, 0x00, sizeof(PSG)); PSG_setVolumeMode (psg, EMU2149_VOL_DEFAULT); psg->clk = c; psg->rate = r ? r : 44100; PSG_set_quality (psg, 0); psg->stereo_mask[0] = 0x03; psg->stereo_mask[1] = 0x03; psg->stereo_mask[2] = 0x03; return psg; } EMU2149_API void PSG_setFlags (PSG * psg, e_uint8 flags) { if (flags & EMU2149_ZX_STEREO) { // ABC Stereo psg->stereo_mask[0] = 0x01; psg->stereo_mask[1] = 0x03; psg->stereo_mask[2] = 0x02; } else { psg->stereo_mask[0] = 0x03; psg->stereo_mask[1] = 0x03; psg->stereo_mask[2] = 0x03; } return; } EMU2149_API void PSG_setVolumeMode (PSG * psg, int type) { switch (type) { case 1: psg->voltbl = voltbl[EMU2149_VOL_YM2149]; break; case 2: psg->voltbl = voltbl[EMU2149_VOL_AY_3_8910]; break; default: psg->voltbl = voltbl[EMU2149_VOL_DEFAULT]; break; } } EMU2149_API e_uint32 PSG_setMask (PSG *psg, e_uint32 mask) { e_uint32 ret = 0; if(psg) { ret = psg->mask; psg->mask = mask; } return ret; } EMU2149_API void PSG_setStereoMask (PSG *psg, e_uint32 mask) { // e_uint32 ret = 0; if(psg) { psg->stereo_mask[0] = (mask >>0) &3; psg->stereo_mask[1] = (mask >>2) &3; psg->stereo_mask[2] = (mask >>4) &3; } } EMU2149_API e_uint32 PSG_toggleMask (PSG *psg, e_uint32 mask) { e_uint32 ret = 0; if(psg) { ret = psg->mask; psg->mask ^= mask; } return ret; } EMU2149_API void PSG_reset (PSG * psg) { int i; psg->base_count = 0; for (i = 0; i < 3; i++) { psg->cout[i] = 0; psg->count[i] = 0x1000; psg->freq[i] = 0; psg->edge[i] = 0; psg->volume[i] = 0; } psg->mask = 0; for (i = 0; i < 16; i++) psg->reg[i] = 0; psg->adr = 0; psg->noise_seed = 0xffff; psg->noise_count = 0x40; psg->noise_freq = 0; psg->env_volume = 0; psg->env_ptr = 0; psg->env_freq = 0; psg->env_count = 0; psg->env_pause = 1; psg->out = 0; } EMU2149_API void PSG_delete (PSG * psg) { free (psg); } EMU2149_API e_uint8 PSG_readIO (PSG * psg) { return (e_uint8) (psg->reg[psg->adr]); } EMU2149_API e_uint8 PSG_readReg (PSG * psg, e_uint32 reg) { return (e_uint8) (psg->reg[reg & 0x1f]); } EMU2149_API void PSG_writeIO (PSG * psg, e_uint32 adr, e_uint32 val) { if (adr & 1) PSG_writeReg (psg, psg->adr, val); else psg->adr = val & 0x1f; } INLINE static e_int16 calc (PSG * psg) { int i, noise; e_uint32 incr; e_int32 mix = 0; psg->base_count += psg->base_incr; incr = (psg->base_count >> GETA_BITS); psg->base_count &= (1 << GETA_BITS) - 1; /* Envelope */ psg->env_count += incr; while (psg->env_count>=0x10000 && psg->env_freq!=0) { if (!psg->env_pause) { if(psg->env_face) psg->env_ptr = (psg->env_ptr + 1) & 0x3f ; else psg->env_ptr = (psg->env_ptr + 0x3f) & 0x3f; } if (psg->env_ptr & 0x20) /* if carry or borrow */ { if (psg->env_continue) { if (psg->env_alternate^psg->env_hold) psg->env_face ^= 1; if (psg->env_hold) psg->env_pause = 1; psg->env_ptr = psg->env_face?0:0x1f; } else { psg->env_pause = 1; psg->env_ptr = 0; } } psg->env_count -= psg->env_freq; } /* Noise */ psg->noise_count += incr; if (psg->noise_count & 0x40) { if (psg->noise_seed & 1) psg->noise_seed ^= 0x24000; psg->noise_seed >>= 1; psg->noise_count -= psg->noise_freq; } noise = psg->noise_seed & 1; /* Tone */ for (i = 0; i < 3; i++) { psg->count[i] += incr; if (psg->count[i] & 0x1000) { if (psg->freq[i] > 1) { psg->edge[i] = !psg->edge[i]; psg->count[i] -= psg->freq[i]; } else { psg->edge[i] = 1; } } psg->cout[i] = 0; // BS maintaining cout for stereo mix if (psg->mask&PSG_MASK_CH(i)) continue; if ((psg->tmask[i] || psg->edge[i]) && (psg->nmask[i] || noise)) { if (!(psg->volume[i] & 32)) psg->cout[i] = psg->voltbl[psg->volume[i] & 31]; else psg->cout[i] = psg->voltbl[psg->env_ptr]; mix += psg->cout[i]; } } return (e_int16) mix; } EMU2149_API e_int16 PSG_calc (PSG * psg) { if (!psg->quality) return (e_int16) (calc (psg) << 4); /* Simple rate converter */ while (psg->realstep > psg->psgtime) { psg->psgtime += psg->psgstep; psg->out += calc (psg); psg->out >>= 1; } psg->psgtime = psg->psgtime - psg->realstep; return (e_int16) (psg->out << 4); } INLINE static void calc_stereo (PSG * psg, e_int32 out[2]) { int i, noise; e_uint32 incr; e_int32 l = 0, r = 0; psg->base_count += psg->base_incr; incr = (psg->base_count >> GETA_BITS); psg->base_count &= (1 << GETA_BITS) - 1; /* Envelope */ psg->env_count += incr; while (psg->env_count>=0x10000 && psg->env_freq!=0) { if (!psg->env_pause) { if(psg->env_face) psg->env_ptr = (psg->env_ptr + 1) & 0x3f ; else psg->env_ptr = (psg->env_ptr + 0x3f) & 0x3f; } if (psg->env_ptr & 0x20) /* if carry or borrow */ { if (psg->env_continue) { if (psg->env_alternate^psg->env_hold) psg->env_face ^= 1; if (psg->env_hold) psg->env_pause = 1; psg->env_ptr = psg->env_face?0:0x1f; } else { psg->env_pause = 1; psg->env_ptr = 0; } } psg->env_count -= psg->env_freq; } /* Noise */ psg->noise_count += incr; if (psg->noise_count & 0x40) { if (psg->noise_seed & 1) psg->noise_seed ^= 0x24000; psg->noise_seed >>= 1; psg->noise_count -= psg->noise_freq; } noise = psg->noise_seed & 1; /* Tone */ for (i = 0; i < 3; i++) { psg->count[i] += incr; if (psg->count[i] & 0x1000) { if (psg->freq[i] > 1) { psg->edge[i] = !psg->edge[i]; psg->count[i] -= psg->freq[i]; } else { psg->edge[i] = 1; } } psg->cout[i] = 0; // BS maintaining cout for stereo mix if (psg->mask&PSG_MASK_CH(i)) continue; if ((psg->tmask[i] || psg->edge[i]) && (psg->nmask[i] || noise)) { if (!(psg->volume[i] & 32)) psg->cout[i] = psg->voltbl[psg->volume[i] & 31]; else psg->cout[i] = psg->voltbl[psg->env_ptr]; if (psg->stereo_mask[i] & 0x01) l += psg->cout[i]; if (psg->stereo_mask[i] & 0x02) r += psg->cout[i]; } } out[0] = l << 5; out[1] = r << 5; return; } EMU2149_API void PSG_calc_stereo (PSG * psg, e_int32 **out, e_int32 samples) { e_int32 *bufMO = out[0]; e_int32 *bufRO = out[1]; e_int32 buffers[2]; int i; for (i = 0; i < samples; i ++) { if (!psg->quality) { calc_stereo (psg, buffers); bufMO[i] = buffers[0]; bufRO[i] = buffers[1]; } else { while (psg->realstep > psg->psgtime) { psg->psgtime += psg->psgstep; psg->sprev[0] = psg->snext[0]; psg->sprev[1] = psg->snext[1]; calc_stereo (psg, psg->snext); } psg->psgtime -= psg->realstep; bufMO[i] = (e_int32) (((double) psg->snext[0] * (psg->psgstep - psg->psgtime) + (double) psg->sprev[0] * psg->psgtime) / psg->psgstep); bufRO[i] = (e_int32) (((double) psg->snext[1] * (psg->psgstep - psg->psgtime) + (double) psg->sprev[1] * psg->psgtime) / psg->psgstep); } } } EMU2149_API void PSG_writeReg (PSG * psg, e_uint32 reg, e_uint32 val) { int c; if (reg > 15) return; psg->reg[reg] = (e_uint8) (val & 0xff); switch (reg) { case 0: case 2: case 4: case 1: case 3: case 5: c = reg >> 1; psg->freq[c] = ((psg->reg[c * 2 + 1] & 15) << 8) + psg->reg[c * 2]; break; case 6: psg->noise_freq = (val == 0) ? 1 : ((val & 31) << 1); break; case 7: psg->tmask[0] = (val & 1); psg->tmask[1] = (val & 2); psg->tmask[2] = (val & 4); psg->nmask[0] = (val & 8); psg->nmask[1] = (val & 16); psg->nmask[2] = (val & 32); break; case 8: case 9: case 10: psg->volume[reg - 8] = val << 1; break; case 11: case 12: psg->env_freq = (psg->reg[12] << 8) + psg->reg[11]; break; case 13: psg->env_continue = (val >> 3) & 1; psg->env_attack = (val >> 2) & 1; psg->env_alternate = (val >> 1) & 1; psg->env_hold = val & 1; psg->env_face = psg->env_attack; psg->env_pause = 0; psg->env_count = 0x10000 - psg->env_freq; psg->env_ptr = psg->env_face?0:0x1f; break; case 14: case 15: default: break; } return; } BambooTracker-0.3.5/BambooTracker/chips/mame/emu2149.h000066400000000000000000000045731362177441300222720ustar00rootroot00000000000000/* emu2149.h */ #ifndef _EMU2149_H_ #define _EMU2149_H_ #include "emutypes.h" /*#ifdef EMU2149_DLL_EXPORTS #define EMU2149_API __declspec(dllexport) #elif EMU2149_DLL_IMPORTS #define EMU2149_API __declspec(dllimport) #else*/ #define EMU2149_API //#endif #define EMU2149_VOL_DEFAULT 1 #define EMU2149_VOL_YM2149 0 #define EMU2149_VOL_AY_3_8910 1 #define EMU2149_ZX_STEREO 0x80 #define PSG_MASK_CH(x) (1<<(x)) /*#ifdef __cplusplus extern "C" { #endif*/ typedef struct __PSG { /* Volume Table */ e_uint32 *voltbl; e_uint8 reg[0x20]; e_int32 out; e_int32 cout[3]; e_uint32 clk, rate, base_incr, quality; e_uint32 count[3]; e_uint32 volume[3]; e_uint32 freq[3]; e_uint32 edge[3]; e_uint32 tmask[3]; e_uint32 nmask[3]; e_uint32 mask; e_uint32 stereo_mask[3]; e_uint32 base_count; e_uint32 env_volume; e_uint32 env_ptr; e_uint32 env_face; e_uint32 env_continue; e_uint32 env_attack; e_uint32 env_alternate; e_uint32 env_hold; e_uint32 env_pause; e_uint32 env_reset; e_uint32 env_freq; e_uint32 env_count; e_uint32 noise_seed; e_uint32 noise_count; e_uint32 noise_freq; /* rate converter */ e_uint32 realstep; e_uint32 psgtime; e_uint32 psgstep; e_int32 prev, next; e_int32 sprev[2], snext[2]; /* I/O Ctrl */ e_uint32 adr; } PSG; EMU2149_API void PSG_set_quality (PSG * psg, e_uint32 q); EMU2149_API void PSG_set_clock(PSG * psg, e_uint32 c); EMU2149_API void PSG_set_rate (PSG * psg, e_uint32 r); EMU2149_API PSG *PSG_new (e_uint32 clk, e_uint32 rate); EMU2149_API void PSG_reset (PSG *); EMU2149_API void PSG_delete (PSG *); EMU2149_API void PSG_writeReg (PSG *, e_uint32 reg, e_uint32 val); EMU2149_API void PSG_writeIO (PSG * psg, e_uint32 adr, e_uint32 val); EMU2149_API e_uint8 PSG_readReg (PSG * psg, e_uint32 reg); EMU2149_API e_uint8 PSG_readIO (PSG * psg); EMU2149_API e_int16 PSG_calc (PSG *); EMU2149_API void PSG_calc_stereo (PSG * psg, e_int32 **out, e_int32 samples); EMU2149_API void PSG_setFlags (PSG * psg, e_uint8 flags); EMU2149_API void PSG_setVolumeMode (PSG * psg, int type); EMU2149_API e_uint32 PSG_setMask (PSG *, e_uint32 mask); EMU2149_API e_uint32 PSG_toggleMask (PSG *, e_uint32 mask); EMU2149_API void PSG_setStereoMask (PSG *psg, e_uint32 mask); /*#ifdef __cplusplus } #endif*/ #endif BambooTracker-0.3.5/BambooTracker/chips/mame/emutypes.h000066400000000000000000000010121362177441300230200ustar00rootroot00000000000000#ifndef _EMUTYPES_H_ #define _EMUTYPES_H_ #ifndef INLINE #if defined(_MSC_VER) //#define INLINE __forceinline #define INLINE __inline #elif defined(__GNUC__) #define INLINE __inline__ #elif defined(_MWERKS_) #define INLINE inline #else #define INLINE #endif #endif typedef unsigned int e_uint; typedef signed int e_int; typedef unsigned char e_uint8 ; typedef signed char e_int8 ; typedef unsigned short e_uint16 ; typedef signed short e_int16 ; typedef unsigned int e_uint32 ; typedef signed int e_int32 ; #endif BambooTracker-0.3.5/BambooTracker/chips/mame/fm.c000066400000000000000000004576621362177441300215740ustar00rootroot00000000000000#define YM2610B_WARNING /* ** ** File: fm.c -- software implementation of Yamaha FM sound generator ** ** Copyright Jarek Burczynski (bujar at mame dot net) ** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development ** ** Version 1.4.2 (final beta) ** */ /* ** History: ** ** 2006-2008 Eke-Eke (Genesis Plus GX), MAME backport by R. Belmont. ** - implemented PG overflow, aka "detune bug" (Ariel, Comix Zone, Shaq Fu, Spiderman,...), credits to Nemesis ** - fixed SSG-EG support, credits to Nemesis and additional fixes from Alone Coder ** - modified EG rates and frequency, tested by Nemesis on real hardware ** - implemented LFO phase update for CH3 special mode (Warlock birds, Alladin bug sound) ** - fixed Attack Rate update (Batman & Robin intro) ** - fixed attenuation level at the start of Substain (Gynoug explosions) ** - fixed EG decay->substain transition to handle special cases, like SL=0 and Decay rate is very slow (Mega Turrican tracks 03,09...) ** ** 06-23-2007 Zsolt Vasvari: ** - changed the timing not to require the use of floating point calculations ** ** 03-08-2003 Jarek Burczynski: ** - fixed YM2608 initial values (after the reset) ** - fixed flag and irqmask handling (YM2608) ** - fixed BUFRDY flag handling (YM2608) ** ** 14-06-2003 Jarek Burczynski: ** - implemented all of the YM2608 status register flags ** - implemented support for external memory read/write via YM2608 ** - implemented support for deltat memory limit register in YM2608 emulation ** ** 22-05-2003 Jarek Burczynski: ** - fixed LFO PM calculations (copy&paste bugfix) ** ** 08-05-2003 Jarek Burczynski: ** - fixed SSG support ** ** 22-04-2003 Jarek Burczynski: ** - implemented 100% correct LFO generator (verified on real YM2610 and YM2608) ** ** 15-04-2003 Jarek Burczynski: ** - added support for YM2608's register 0x110 - status mask ** ** 01-12-2002 Jarek Burczynski: ** - fixed register addressing in YM2608, YM2610, YM2610B chips. (verified on real YM2608) ** The addressing patch used for early Neo-Geo games can be removed now. ** ** 26-11-2002 Jarek Burczynski, Nicola Salmoria: ** - recreated YM2608 ADPCM ROM using data from real YM2608's output which leads to: ** - added emulation of YM2608 drums. ** - output of YM2608 is two times lower now - same as YM2610 (verified on real YM2608) ** ** 16-08-2002 Jarek Burczynski: ** - binary exact Envelope Generator (verified on real YM2203); ** identical to YM2151 ** - corrected 'off by one' error in feedback calculations (when feedback is off) ** - corrected connection (algorithm) calculation (verified on real YM2203 and YM2610) ** ** 18-12-2001 Jarek Burczynski: ** - added SSG-EG support (verified on real YM2203) ** ** 12-08-2001 Jarek Burczynski: ** - corrected sin_tab and tl_tab data (verified on real chip) ** - corrected feedback calculations (verified on real chip) ** - corrected phase generator calculations (verified on real chip) ** - corrected envelope generator calculations (verified on real chip) ** - corrected FM volume level (YM2610 and YM2610B). ** - changed YMxxxUpdateOne() functions (YM2203, YM2608, YM2610, YM2610B, YM2612) : ** this was needed to calculate YM2610 FM channels output correctly. ** (Each FM channel is calculated as in other chips, but the output of the channel ** gets shifted right by one *before* sending to accumulator. That was impossible to do ** with previous implementation). ** ** 23-07-2001 Jarek Burczynski, Nicola Salmoria: ** - corrected YM2610 ADPCM type A algorithm and tables (verified on real chip) ** ** 11-06-2001 Jarek Burczynski: ** - corrected end of sample bug in ADPCMA_calc_cha(). ** Real YM2610 checks for equality between current and end addresses (only 20 LSB bits). ** ** 08-12-98 hiro-shi: ** rename ADPCMA -> ADPCMB, ADPCMB -> ADPCMA ** move ROM limit check.(CALC_CH? -> 2610Write1/2) ** test program (ADPCMB_TEST) ** move ADPCM A/B end check. ** ADPCMB repeat flag(no check) ** change ADPCM volume rate (8->16) (32->48). ** ** 09-12-98 hiro-shi: ** change ADPCM volume. (8->16, 48->64) ** replace ym2610 ch0/3 (YM-2610B) ** change ADPCM_SHIFT (10->8) missing bank change 0x4000-0xffff. ** add ADPCM_SHIFT_MASK ** change ADPCMA_DECODE_MIN/MAX. */ /************************************************************************/ /* comment of hiro-shi(Hiromitsu Shioya) */ /* YM2610(B) = OPN-B */ /* YM2610 : PSG:3ch FM:4ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ /* YM2610B : PSG:3ch FM:6ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ /************************************************************************/ #include #include #include #include #include #include "mamedef.h" //#ifndef __RAINE__ //#include "sndintrf.h" /* use M.A.M.E. */ //#else //#include "deftypes.h" /* use RAINE */ //#include "support.h" /* use RAINE */ //#endif #include "fm.h" /* include external DELTA-T unit (when needed) */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) #include "ymdeltat.h" #endif /* shared function building option */ #define BUILD_OPN (BUILD_YM2203||BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) #define BUILD_OPN_PRESCALER (BUILD_YM2203||BUILD_YM2608) /* globals */ #define TYPE_SSG 0x01 /* SSG support */ #define TYPE_LFOPAN 0x02 /* OPN type LFO and PAN */ #define TYPE_6CH 0x04 /* FM 6CH / 3CH */ #define TYPE_DAC 0x08 /* YM2612's DAC device */ #define TYPE_ADPCM 0x10 /* two ADPCM units */ #define TYPE_2610 0x20 /* bogus flag to differentiate 2608 from 2610 */ #define TYPE_YM2203 (TYPE_SSG) #define TYPE_YM2608 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM) #define TYPE_YM2610 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM |TYPE_2610) #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>3) /* sin waveform table in 'decibel' scale */ static unsigned int sin_tab[SIN_LEN]; /* sustain level table (3dB per step) */ /* bit0, bit1, bit2, bit3, bit4, bit5, bit6 */ /* 1, 2, 4, 8, 16, 32, 64 (value)*/ /* 0.75, 1.5, 3, 6, 12, 24, 48 (dB)*/ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ #define SC(db) (UINT32) ( db * (4.0/ENV_STEP) ) static const UINT32 sl_table[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const UINT8 eg_inc[19*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ /*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ /*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ /*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ /*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ /*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ /*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /*note that there is no O(17) in this table - it's directly in the code */ static const UINT8 eg_rate_select[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ /* 32 infinite time rates */ O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), /* rates 00-11 */ O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 12 */ O( 4),O( 5),O( 6),O( 7), /* rate 13 */ O( 8),O( 9),O(10),O(11), /* rate 14 */ O(12),O(13),O(14),O(15), /* rate 15 */ O(16),O(16),O(16),O(16), /* 32 dummy rates (same as 15 3) */ O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ /*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ /*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ #define O(a) (a*1) static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ /* 32 infinite time rates */ O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), /* rates 00-11 */ O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 12 */ O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 32 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) }; #undef O static const UINT8 dt_tab[4 * 32]={ /* this is YM2151 and YM2612 phase increment data (in 10.10 fixed point format)*/ /* FD=0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* FD=1 */ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, /* FD=2 */ 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, /* FD=3 */ 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 }; /* OPN key frequency number -> key code follow table */ /* fnum higher 4bit -> keycode lower 2bit */ static const UINT8 opn_fktable[16] = {0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3}; /* 8 LFO speed parameters */ /* each value represents number of samples that one LFO level will last for */ static const UINT32 lfo_samples_per_step[8] = {108, 77, 71, 67, 62, 44, 8, 5}; /*There are 4 different LFO AM depths available, they are: 0 dB, 1.4 dB, 5.9 dB, 11.8 dB Here is how it is generated (in EG steps): 11.8 dB = 0, 2, 4, 6, 8, 10,12,14,16...126,126,124,122,120,118,....4,2,0 5.9 dB = 0, 1, 2, 3, 4, 5, 6, 7, 8....63, 63, 62, 61, 60, 59,.....2,1,0 1.4 dB = 0, 0, 0, 0, 1, 1, 1, 1, 2,...15, 15, 15, 15, 14, 14,.....0,0,0 (1.4 dB is losing precision as you can see) It's implemented as generator from 0..126 with step 2 then a shift right N times, where N is: 8 for 0 dB 3 for 1.4 dB 1 for 5.9 dB 0 for 11.8 dB */ static const UINT8 lfo_ams_depth_shift[4] = {8, 3, 1, 0}; /*There are 8 different LFO PM depths available, they are: 0, 3.4, 6.7, 10, 14, 20, 40, 80 (cents) Modulation level at each depth depends on F-NUMBER bits: 4,5,6,7,8,9,10 (bits 8,9,10 = FNUM MSB from OCT/FNUM register) Here we store only first quarter (positive one) of full waveform. Full table (lfo_pm_table) containing all 128 waveforms is build at run (init) time. One value in table below represents 4 (four) basic LFO steps (1 PM step = 4 AM steps). For example: at LFO SPEED=0 (which is 108 samples per basic LFO step) one value from "lfo_pm_output" table lasts for 432 consecutive samples (4*108=432) and one full LFO waveform cycle lasts for 13824 samples (32*432=13824; 32 because we store only a quarter of whole waveform in the table below) */ static const UINT8 lfo_pm_output[7*8][8]={ /* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */ /* FNUM BIT 4: 000 0001xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* FNUM BIT 5: 000 0010xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* FNUM BIT 6: 000 0100xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1}, /* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* FNUM BIT 7: 000 1000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1}, /* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2}, /* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3}, /* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* FNUM BIT 8: 001 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1}, /* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2}, /* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3}, /* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4}, /* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6}, /* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* FNUM BIT 9: 010 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2}, /* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4}, /* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6}, /* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8}, /* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, /* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, /* FNUM BIT10: 100 0000xxxx */ /* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4}, /* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8}, /* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc}, /* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10}, /* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, /* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, /* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60}, }; /* all 128 LFO PM waveforms */ static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (of F-NUMBER), 8 LFO depths, 32 LFO output levels per one depth */ /* register number to channel number , slot offset */ #define OPN_CHAN(N) (N&3) #define OPN_SLOT(N) ((N>>2)&3) /* slot number */ #define SLOT1 0 #define SLOT2 2 #define SLOT3 1 #define SLOT4 3 /* bit0 = Right enable , bit1 = Left enable */ #define OUTD_RIGHT 1 #define OUTD_LEFT 2 #define OUTD_CENTER 3 /* save output as raw 16-bit sample */ /* #define SAVE_SAMPLE */ #ifdef SAVE_SAMPLE static FILE *sample[1]; #if 1 /*save to MONO file */ #define SAVE_ALL_CHANNELS \ { signed int pom = lt; \ fputc((unsigned short)pom&0xff,sample[0]); \ fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ } #else /*save to STEREO file */ #define SAVE_ALL_CHANNELS \ { signed int pom = lt; \ fputc((unsigned short)pom&0xff,sample[0]); \ fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ pom = rt; \ fputc((unsigned short)pom&0xff,sample[0]); \ fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ } #endif #endif /* struct describing a single operator (SLOT) */ typedef struct { INT32 *DT; /* detune :dt_tab[DT] */ UINT8 KSR; /* key scale rate :3-KSR */ UINT32 ar; /* attack rate */ UINT32 d1r; /* decay rate */ UINT32 d2r; /* sustain rate */ UINT32 rr; /* release rate */ UINT8 ksr; /* key scale rate :kcode>>(3-KSR) */ UINT32 mul; /* multiple :ML_TABLE[ML] */ /* Phase Generator */ UINT32 phase; /* phase counter */ INT32 Incr; /* phase step */ /* Envelope Generator */ UINT8 state; /* phase type */ UINT32 tl; /* total level: TL << 3 */ INT32 volume; /* envelope counter */ UINT32 sl; /* sustain level:sl_table[SL] */ UINT32 vol_out; /* current output from EG circuit (without AM from LFO) */ UINT8 eg_sh_ar; /* (attack state) */ UINT8 eg_sel_ar; /* (attack state) */ UINT8 eg_sh_d1r; /* (decay state) */ UINT8 eg_sel_d1r; /* (decay state) */ UINT8 eg_sh_d2r; /* (sustain state) */ UINT8 eg_sel_d2r; /* (sustain state) */ UINT8 eg_sh_rr; /* (release state) */ UINT8 eg_sel_rr; /* (release state) */ UINT8 ssg; /* SSG-EG waveform */ UINT8 ssgn; /* SSG-EG negated output */ UINT32 key; /* 0=last key was KEY OFF, 1=KEY ON */ /* LFO */ UINT32 AMmask; /* AM enable flag */ } FM_SLOT; typedef struct { FM_SLOT SLOT[4]; /* four SLOTs (operators) */ UINT8 ALGO; /* algorithm */ UINT8 FB; /* feedback shift */ INT32 op1_out[2]; /* op1 output for feedback */ INT32 *connect1; /* SLOT1 output pointer */ INT32 *connect3; /* SLOT3 output pointer */ INT32 *connect2; /* SLOT2 output pointer */ INT32 *connect4; /* SLOT4 output pointer */ INT32 *mem_connect;/* where to put the delayed sample (MEM) */ INT32 mem_value; /* delayed sample (MEM) value */ INT32 pms; /* channel PMS */ UINT8 ams; /* channel AMS */ UINT32 fc; /* fnum,blk:adjusted to sample rate */ UINT8 kcode; /* key code: */ UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ UINT8 Muted; } FM_CH; typedef struct { //const device_config *device; void * param; /* this chip parameter */ int clock; /* master clock (Hz) */ int rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ int timer_prescaler; /* timer prescaler */ #if FM_BUSY_FLAG_SUPPORT TIME_TYPE busy_expiry_time; /* expiry time of the busy status */ #endif UINT8 address; /* address register */ UINT8 irq; /* interrupt level */ UINT8 irqmask; /* irq mask */ UINT8 status; /* status flag */ UINT32 mode; /* mode CSM / 3SLOT */ UINT8 prescaler_sel; /* prescaler selector */ UINT8 fn_h; /* freq latch */ INT32 TA; /* timer a */ INT32 TAC; /* timer a counter */ UINT8 TB; /* timer b */ INT32 TBC; /* timer b counter */ /* local time tables */ INT32 dt_tab[8][32]; /* DeTune table */ /* Extention Timer and IRQ handler */ FM_TIMERHANDLER timer_handler; FM_IRQHANDLER IRQ_Handler; const ssg_callbacks *SSG; } FM_ST; /***********************************************************/ /* OPN unit */ /***********************************************************/ /* OPN 3slot struct */ typedef struct { UINT32 fc[3]; /* fnum3,blk3: calculated */ UINT8 fn_h; /* freq3 latch */ UINT8 kcode[3]; /* key code */ UINT32 block_fnum[3]; /* current fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ } FM_3SLOT; /* OPN/A/B common state */ typedef struct { UINT8 type; /* chip type */ FM_ST ST; /* general state */ FM_3SLOT SL3; /* 3 slot mode state */ FM_CH *P_CH; /* pointer of CH */ unsigned int pan[6*2]; /* fm channels output masks (0xffffffff = enable) */ UINT32 eg_cnt; /* global envelope generator counter */ UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/64/3 */ UINT32 eg_timer_add; /* step of eg_timer */ UINT32 eg_timer_overflow;/* envelope generator timer overlfows every 3 samples (on real chip) */ /* there are 2048 FNUMs that can be generated using FNUM/BLK registers but LFO works with one more bit of a precision so we really need 4096 elements */ UINT32 fn_table[4096]; /* fnumber->increment counter */ UINT32 fn_max; /* maximal phase increment (used for phase overflow) */ /* LFO */ UINT32 LFO_AM; /* runtime LFO calculations helper */ INT32 LFO_PM; /* runtime LFO calculations helper */ UINT32 lfo_cnt; UINT32 lfo_inc; UINT32 lfo_freq[8]; /* LFO FREQ table */ INT32 m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ INT32 mem; /* one sample delay memory */ INT32 out_fm[8]; /* outputs of working channels */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) INT32 out_adpcm[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 ADPCM */ INT32 out_delta[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 DELTAT*/ #endif } FM_OPN; /* log output level */ #define LOG_ERR 3 /* ERROR */ #define LOG_WAR 2 /* WARNING */ #define LOG_INF 1 /* INFORMATION */ #define LOG_LEVEL LOG_INF #ifndef __RAINE__ #define LOG(n,x) do { if( (n)>=LOG_LEVEL ) logerror x; } while (0) #endif /* limitter */ #define Limit(val, max,min) { \ if ( val > max ) val = max; \ else if ( val < min ) val = min; \ } /* status set and IRQ handling */ INLINE void FM_STATUS_SET(FM_ST *ST,int flag) { /* set status flag */ ST->status |= flag; if ( !(ST->irq) && (ST->status & ST->irqmask) ) { ST->irq = 1; /* callback user interrupt handler (IRQ is OFF to ON) */ if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,1); } } /* status reset and IRQ handling */ INLINE void FM_STATUS_RESET(FM_ST *ST,int flag) { /* reset status flag */ ST->status &=~flag; if ( (ST->irq) && !(ST->status & ST->irqmask) ) { ST->irq = 0; /* callback user interrupt handler (IRQ is ON to OFF) */ if(ST->IRQ_Handler) (ST->IRQ_Handler)(ST->param,0); } } /* IRQ mask set */ INLINE void FM_IRQMASK_SET(FM_ST *ST,int flag) { ST->irqmask = flag; /* IRQ handling check */ FM_STATUS_SET(ST,0); FM_STATUS_RESET(ST,0); } /* OPN Mode Register Write */ INLINE void set_timers( FM_ST *ST, void *n, int v ) { /* b7 = CSM MODE */ /* b6 = 3 slot mode */ /* b5 = reset b */ /* b4 = reset a */ /* b3 = timer enable b */ /* b2 = timer enable a */ /* b1 = load b */ /* b0 = load a */ ST->mode = v; /* reset Timer b flag */ if( v & 0x20 ) FM_STATUS_RESET(ST,0x02); /* reset Timer a flag */ if( v & 0x10 ) FM_STATUS_RESET(ST,0x01); /* load b */ if( v & 0x02 ) { if( ST->TBC == 0 ) { ST->TBC = ( 256-ST->TB)<<4; /* External timer handler */ if (ST->timer_handler) (ST->timer_handler)(n,1,ST->TBC * ST->timer_prescaler,ST->clock); } } else { /* stop timer b */ if( ST->TBC != 0 ) { ST->TBC = 0; if (ST->timer_handler) (ST->timer_handler)(n,1,0,ST->clock); } } /* load a */ if( v & 0x01 ) { if( ST->TAC == 0 ) { ST->TAC = (1024-ST->TA); /* External timer handler */ if (ST->timer_handler) (ST->timer_handler)(n,0,ST->TAC * ST->timer_prescaler,ST->clock); } } else { /* stop timer a */ if( ST->TAC != 0 ) { ST->TAC = 0; if (ST->timer_handler) (ST->timer_handler)(n,0,0,ST->clock); } } } /* Timer A Overflow */ INLINE void TimerAOver(FM_ST *ST) { /* set status (if enabled) */ if(ST->mode & 0x04) FM_STATUS_SET(ST,0x01); /* clear or reload the counter */ ST->TAC = (1024-ST->TA); if (ST->timer_handler) (ST->timer_handler)(ST->param,0,ST->TAC * ST->timer_prescaler,ST->clock); } /* Timer B Overflow */ INLINE void TimerBOver(FM_ST *ST) { /* set status (if enabled) */ if(ST->mode & 0x08) FM_STATUS_SET(ST,0x02); /* clear or reload the counter */ ST->TBC = ( 256-ST->TB)<<4; if (ST->timer_handler) (ST->timer_handler)(ST->param,1,ST->TBC * ST->timer_prescaler,ST->clock); } #if FM_INTERNAL_TIMER /* ----- internal timer mode , update timer */ /* ---------- calculate timer A ---------- */ #define INTERNAL_TIMER_A(ST,CSM_CH) \ { \ if( (ST)->TAC && ((ST)->timer_handler==0) ) \ if( ((ST)->TAC -= (int)((ST)->freqbase*4096)) <= 0 ) \ { \ TimerAOver( ST ); \ /* CSM mode total level latch and auto key on */ \ if( (ST)->mode & 0x80 ) \ CSMKeyControll( OPN->type, CSM_CH ); \ } \ } /* ---------- calculate timer B ---------- */ #define INTERNAL_TIMER_B(ST,step) \ { \ if( (ST)->TBC && ((ST)->timer_handler==0) ) \ if( ((ST)->TBC -= (int)((ST)->freqbase*4096*step)) <= 0 ) \ TimerBOver( ST ); \ } #else /* FM_INTERNAL_TIMER */ /* external timer mode */ #define INTERNAL_TIMER_A(ST,CSM_CH) #define INTERNAL_TIMER_B(ST,step) #endif /* FM_INTERNAL_TIMER */ #if FM_BUSY_FLAG_SUPPORT #define FM_BUSY_CLEAR(ST) ((ST)->busy_expiry_time = UNDEFINED_TIME) INLINE UINT8 FM_STATUS_FLAG(FM_ST *ST) { if( COMPARE_TIMES(ST->busy_expiry_time, UNDEFINED_TIME) != 0 ) { if (COMPARE_TIMES(ST->busy_expiry_time, FM_GET_TIME_NOW(ST->device->machine)) > 0) return ST->status | 0x80; /* with busy */ /* expire */ FM_BUSY_CLEAR(ST); } return ST->status; } INLINE void FM_BUSY_SET(FM_ST *ST,int busyclock ) { TIME_TYPE expiry_period = MULTIPLY_TIME_BY_INT(ATTOTIME_IN_HZ(ST->clock), busyclock * ST->timer_prescaler); ST->busy_expiry_time = ADD_TIMES(FM_GET_TIME_NOW(ST->device->machine), expiry_period); } #else #define FM_STATUS_FLAG(ST) ((ST)->status) #define FM_BUSY_SET(ST,bclock) {} #define FM_BUSY_CLEAR(ST) {} #endif INLINE void FM_KEYON(UINT8 type, FM_CH *CH , int s ) { (void)type; FM_SLOT *SLOT = &CH->SLOT[s]; if( !SLOT->key ) { SLOT->key = 1; SLOT->phase = 0; /* restart Phase Generator */ SLOT->ssgn = (SLOT->ssg & 0x04) >> 1; SLOT->state = EG_ATT; } } INLINE void FM_KEYOFF(FM_CH *CH , int s ) { FM_SLOT *SLOT = &CH->SLOT[s]; if( SLOT->key ) { SLOT->key = 0; if (SLOT->state>EG_REL) SLOT->state = EG_REL;/* phase -> Release */ } } /* set algorithm connection */ static void setup_connection( FM_OPN *OPN, FM_CH *CH, int ch ) { INT32 *carrier = &OPN->out_fm[ch]; INT32 **om1 = &CH->connect1; INT32 **om2 = &CH->connect3; INT32 **oc1 = &CH->connect2; INT32 **memc = &CH->mem_connect; switch( CH->ALGO ) { case 0: /* M1---C1---MEM---M2---C2---OUT */ *om1 = &OPN->c1; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 1: /* M1------+-MEM---M2---C2---OUT */ /* C1-+ */ *om1 = &OPN->mem; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 2: /* M1-----------------+-C2---OUT */ /* C1---MEM---M2-+ */ *om1 = &OPN->c2; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->m2; break; case 3: /* M1---C1---MEM------+-C2---OUT */ /* M2-+ */ *om1 = &OPN->c1; *oc1 = &OPN->mem; *om2 = &OPN->c2; *memc= &OPN->c2; break; case 4: /* M1---C1-+-OUT */ /* M2---C2-+ */ /* MEM: not used */ *om1 = &OPN->c1; *oc1 = carrier; *om2 = &OPN->c2; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; case 5: /* +----C1----+ */ /* M1-+-MEM---M2-+-OUT */ /* +----C2----+ */ *om1 = 0; /* special mark */ *oc1 = carrier; *om2 = carrier; *memc= &OPN->m2; break; case 6: /* M1---C1-+ */ /* M2-+-OUT */ /* C2-+ */ /* MEM: not used */ *om1 = &OPN->c1; *oc1 = carrier; *om2 = carrier; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; case 7: /* M1-+ */ /* C1-+-OUT */ /* M2-+ */ /* C2-+ */ /* MEM: not used*/ *om1 = carrier; *oc1 = carrier; *om2 = carrier; *memc= &OPN->mem; /* store it anywhere where it will not be used */ break; } CH->connect4 = carrier; } /* set detune & multiple */ INLINE void set_det_mul(FM_ST *ST,FM_CH *CH,FM_SLOT *SLOT,int v) { SLOT->mul = (v&0x0f)? (v&0x0f)*2 : 1; SLOT->DT = ST->dt_tab[(v>>4)&7]; CH->SLOT[SLOT1].Incr=-1; } /* set total level */ INLINE void set_tl(FM_CH *CH,FM_SLOT *SLOT , int v) { (void)CH; SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ } /* set attack rate & key scale */ INLINE void set_ar_ksr(UINT8 type, FM_CH *CH,FM_SLOT *SLOT,int v) { (void)type; UINT8 old_KSR = SLOT->KSR; SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->KSR = 3-(v>>6); if (SLOT->KSR != old_KSR) { CH->SLOT[SLOT1].Incr=-1; } /* refresh Attack rate */ if ((SLOT->ar + SLOT->ksr) < 32+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 17*RATE_STEPS; } } /* set decay rate */ INLINE void set_dr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; } /* set sustain rate */ INLINE void set_sr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; } /* set release rate */ INLINE void set_sl_rr(UINT8 type, FM_SLOT *SLOT,int v) { (void)type; SLOT->sl = sl_table[ v>>4 ]; SLOT->rr = 34 + ((v&0x0f)<<2); SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; } INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm) { UINT32 p; p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm) { UINT32 p; p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } /* advance LFO to next sample */ INLINE void advance_lfo(FM_OPN *OPN) { UINT8 pos; if (OPN->lfo_inc) /* LFO enabled ? */ { OPN->lfo_cnt += OPN->lfo_inc; pos = (OPN->lfo_cnt >> LFO_SH) & 127; /* update AM when LFO output changes */ /* actually I can't optimize is this way without rewriting chan_calc() to use chip->lfo_am instead of global lfo_am */ { /* triangle */ /* AM: 0 to 126 step +2, 126 to 0 step -2 */ if (pos<64) OPN->LFO_AM = (pos&63) * 2; else OPN->LFO_AM = 126 - ((pos&63) * 2); } /* PM works with 4 times slower clock */ pos >>= 2; /* update PM when LFO output changes */ /*if (prev_pos != pos)*/ /* can't use global lfo_pm for this optimization, must be chip->lfo_pm instead*/ { OPN->LFO_PM = pos; } } else { OPN->LFO_AM = 0; OPN->LFO_PM = 0; } } /* changed from INLINE to static here to work around gcc 4.2.1 codegen bug */ static void advance_eg_channel(FM_OPN *OPN, FM_SLOT *SLOT) { unsigned int out; unsigned int swap_flag = 0; unsigned int i; i = 4; /* four operators per channel */ do { /* reset SSG-EG swap flag */ swap_flag = 0; switch(SLOT->state) { case EG_ATT: /* attack phase */ if ( !(OPN->eg_cnt & ((1<eg_sh_ar)-1) ) ) { SLOT->volume += (~SLOT->volume * (eg_inc[SLOT->eg_sel_ar + ((OPN->eg_cnt>>SLOT->eg_sh_ar)&7)]) ) >>4; if (SLOT->volume <= MIN_ATT_INDEX) { SLOT->volume = MIN_ATT_INDEX; SLOT->state = EG_DEC; } } break; case EG_DEC: /* decay phase */ { if (SLOT->ssg&0x08) /* SSG EG type envelope selected */ { if ( !(OPN->eg_cnt & ((1<eg_sh_d1r)-1) ) ) { SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; if ( SLOT->volume >= (INT32)(SLOT->sl) ) SLOT->state = EG_SUS; } } else { if ( !(OPN->eg_cnt & ((1<eg_sh_d1r)-1) ) ) { SLOT->volume += eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; if ( SLOT->volume >= (INT32)(SLOT->sl) ) SLOT->state = EG_SUS; } } } break; case EG_SUS: /* sustain phase */ if (SLOT->ssg&0x08) /* SSG EG type envelope selected */ { if ( !(OPN->eg_cnt & ((1<eg_sh_d2r)-1) ) ) { SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; if ( SLOT->volume >= ENV_QUIET ) { SLOT->volume = MAX_ATT_INDEX; if (SLOT->ssg&0x01) /* bit 0 = hold */ { if (SLOT->ssgn&1) /* have we swapped once ??? */ { /* yes, so do nothing, just hold current level */ } else swap_flag = (SLOT->ssg&0x02) | 1 ; /* bit 1 = alternate */ } else { /* same as KEY-ON operation */ /* restart of the Phase Generator should be here */ SLOT->phase = 0; { /* phase -> Attack */ SLOT->volume = 511; SLOT->state = EG_ATT; } swap_flag = (SLOT->ssg&0x02); /* bit 1 = alternate */ } } } } else { if ( !(OPN->eg_cnt & ((1<eg_sh_d2r)-1) ) ) { SLOT->volume += eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; if ( SLOT->volume >= MAX_ATT_INDEX ) { SLOT->volume = MAX_ATT_INDEX; /* do not change SLOT->state (verified on real chip) */ } } } break; case EG_REL: /* release phase */ if ( !(OPN->eg_cnt & ((1<eg_sh_rr)-1) ) ) { /* SSG-EG affects Release phase also (Nemesis) */ SLOT->volume += eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; if ( SLOT->volume >= MAX_ATT_INDEX ) { SLOT->volume = MAX_ATT_INDEX; SLOT->state = EG_OFF; } } break; } out = ((UINT32)SLOT->volume); /* negate output (changes come from alternate bit, init comes from attack bit) */ if ((SLOT->ssg&0x08) && (SLOT->ssgn&2) && (SLOT->state > EG_REL)) out ^= MAX_ATT_INDEX; /* we need to store the result here because we are going to change ssgn in next instruction */ SLOT->vol_out = out + SLOT->tl; /* reverse SLOT inversion flag */ SLOT->ssgn ^= swap_flag; SLOT++; i--; }while (i); } #define volume_calc(OP) ((OP)->vol_out + (AM & (OP)->AMmask)) INLINE void update_phase_lfo_slot(FM_OPN *OPN, FM_SLOT *SLOT, INT32 pms, UINT32 block_fnum) { UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + OPN->LFO_PM ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { UINT8 blk; UINT32 fn; int kc, fc; block_fnum = block_fnum*2 + lfo_fn_table_index_offset; blk = (block_fnum&0x7000) >> 12; fn = block_fnum & 0xfff; /* keyscale code */ kc = (blk<<2) | opn_fktable[fn >> 8]; /* phase increment counter */ fc = (OPN->fn_table[fn]>>(7-blk)) + SLOT->DT[kc]; /* detects frequency overflow (credits to Nemesis) */ if (fc < 0) fc += OPN->fn_max; /* update phase */ SLOT->phase += (fc * SLOT->mul) >> 1; } else /* LFO phase modulation = zero */ { SLOT->phase += SLOT->Incr; } } INLINE void update_phase_lfo_channel(FM_OPN *OPN, FM_CH *CH) { UINT32 block_fnum = CH->block_fnum; UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + CH->pms + OPN->LFO_PM ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { UINT8 blk; UINT32 fn; int kc, fc, finc; block_fnum = block_fnum*2 + lfo_fn_table_index_offset; blk = (block_fnum&0x7000) >> 12; fn = block_fnum & 0xfff; /* keyscale code */ kc = (blk<<2) | opn_fktable[fn >> 8]; /* phase increment counter */ fc = (OPN->fn_table[fn]>>(7-blk)); /* detects frequency overflow (credits to Nemesis) */ finc = fc + CH->SLOT[SLOT1].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT1].phase += (finc*CH->SLOT[SLOT1].mul) >> 1; finc = fc + CH->SLOT[SLOT2].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT2].phase += (finc*CH->SLOT[SLOT2].mul) >> 1; finc = fc + CH->SLOT[SLOT3].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT3].phase += (finc*CH->SLOT[SLOT3].mul) >> 1; finc = fc + CH->SLOT[SLOT4].DT[kc]; if (finc < 0) finc += OPN->fn_max; CH->SLOT[SLOT4].phase += (finc*CH->SLOT[SLOT4].mul) >> 1; } else /* LFO phase modulation = zero */ { CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; } } INLINE void chan_calc(FM_OPN *OPN, FM_CH *CH, int chnum) { unsigned int eg_out; UINT32 AM = OPN->LFO_AM >> CH->ams; if (CH->Muted) return; OPN->m2 = OPN->c1 = OPN->c2 = OPN->mem = 0; *CH->mem_connect = CH->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ eg_out = volume_calc(&CH->SLOT[SLOT1]); { INT32 out = CH->op1_out[0] + CH->op1_out[1]; CH->op1_out[0] = CH->op1_out[1]; if( !CH->connect1 ) { /* algorithm 5 */ OPN->mem = OPN->c1 = OPN->c2 = CH->op1_out[0]; } else { /* other algorithms */ *CH->connect1 += CH->op1_out[0]; } CH->op1_out[1] = 0; if( eg_out < ENV_QUIET ) /* SLOT 1 */ { if (!CH->FB) out=0; CH->op1_out[1] = op_calc1(CH->SLOT[SLOT1].phase, eg_out, (out<FB) ); } } eg_out = volume_calc(&CH->SLOT[SLOT3]); if( eg_out < ENV_QUIET ) /* SLOT 3 */ *CH->connect3 += op_calc(CH->SLOT[SLOT3].phase, eg_out, OPN->m2); eg_out = volume_calc(&CH->SLOT[SLOT2]); if( eg_out < ENV_QUIET ) /* SLOT 2 */ *CH->connect2 += op_calc(CH->SLOT[SLOT2].phase, eg_out, OPN->c1); eg_out = volume_calc(&CH->SLOT[SLOT4]); if( eg_out < ENV_QUIET ) /* SLOT 4 */ *CH->connect4 += op_calc(CH->SLOT[SLOT4].phase, eg_out, OPN->c2); /* store current MEM */ CH->mem_value = OPN->mem; /* update phase counters AFTER output calculations */ if(CH->pms) { /* add support for 3 slot mode */ if ((OPN->ST.mode & 0xC0) && (chnum == 2)) { update_phase_lfo_slot(OPN, &CH->SLOT[SLOT1], CH->pms, OPN->SL3.block_fnum[1]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT2], CH->pms, OPN->SL3.block_fnum[2]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT3], CH->pms, OPN->SL3.block_fnum[0]); update_phase_lfo_slot(OPN, &CH->SLOT[SLOT4], CH->pms, CH->block_fnum); } else update_phase_lfo_channel(OPN, CH); } else /* no LFO phase modulation */ { CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; } } /* update phase increment and envelope generator */ INLINE void refresh_fc_eg_slot(FM_OPN *OPN, FM_SLOT *SLOT , int fc , int kc ) { int ksr = kc >> SLOT->KSR; fc += SLOT->DT[kc]; /* detects frequency overflow (credits to Nemesis) */ if (fc < 0) fc += OPN->fn_max; /* (frequency) phase increment counter */ SLOT->Incr = (fc * SLOT->mul) >> 1; if( SLOT->ksr != ksr ) { SLOT->ksr = ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 32+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 17*RATE_STEPS; } SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; } } /* update phase increment counters */ /* Changed from INLINE to static to work around gcc 4.2.1 codegen bug */ static void refresh_fc_eg_chan(FM_OPN *OPN, FM_CH *CH ) { if( CH->SLOT[SLOT1].Incr==-1) { int fc = CH->fc; int kc = CH->kcode; refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT1] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT2] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT3] , fc , kc ); refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT4] , fc , kc ); } } /* initialize time tables */ static void init_timetables( FM_ST *ST , const UINT8 *dttable ) { int i,d; double rate; #if 0 logerror("FM.C: samplerate=%8i chip clock=%8i freqbase=%f \n", ST->rate, ST->clock, ST->freqbase ); #endif /* DeTune table */ for (d = 0;d <= 3;d++) { for (i = 0;i <= 31;i++) { rate = ((double)dttable[d*32 + i]) * SIN_LEN * ST->freqbase * (1<dt_tab[d][i] = (INT32) rate; ST->dt_tab[d+4][i] = -ST->dt_tab[d][i]; #if 0 logerror("FM.C: DT [%2i %2i] = %8x \n", d, i, ST->dt_tab[d][i] ); #endif } } } static void reset_channels( FM_ST *ST , FM_CH *CH , int num ) { int c,s; ST->mode = 0; /* normal mode */ ST->TA = 0; ST->TAC = 0; ST->TB = 0; ST->TBC = 0; for( c = 0 ; c < num ; c++ ) { //memset(&CH[c], 0x00, sizeof(FM_CH)); CH[c].mem_value = 0; CH[c].op1_out[0] = 0; CH[c].op1_out[1] = 0; CH[c].fc = 0; for(s = 0 ; s < 4 ; s++ ) { //memset(&CH[c].SLOT[s], 0x00, sizeof(FM_SLOT)); CH[c].SLOT[s].Incr = -1; CH[c].SLOT[s].key = 0; CH[c].SLOT[s].phase = 0; CH[c].SLOT[s].ssg = 0; CH[c].SLOT[s].ssgn = 0; CH[c].SLOT[s].state= EG_OFF; CH[c].SLOT[s].volume = MAX_ATT_INDEX; CH[c].SLOT[s].vol_out= MAX_ATT_INDEX; } } } /* initialize generic tables */ static int init_tables(void) { signed int i,x; signed int n; double o,m; for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 2; /* 13 bits here (as in real chip) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; for (i=1; i<13; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; } #if 0 logerror("tl %04i", x); for (i=0; i<13; i++) logerror(", [%02i] %4x", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ]); logerror("\n"); #endif } /*logerror("FM.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("FM.C: sin [%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[i],tl_tab[sin_tab[i]]);*/ } /*logerror("FM.C: ENV_QUIET= %08x\n",ENV_QUIET );*/ /* build LFO PM modulation table */ for(i = 0; i < 8; i++) /* 8 PM depths */ { UINT8 fnum; for (fnum=0; fnum<128; fnum++) /* 7 bits meaningful of F-NUMBER */ { UINT8 value; UINT8 step; UINT32 offset_depth = i; UINT32 offset_fnum_bit; UINT32 bit_tmp; for (step=0; step<8; step++) { value = 0; for (bit_tmp=0; bit_tmp<7; bit_tmp++) /* 7 bits */ { if (fnum & (1<SLOT[SLOT1].key) { FM_KEYON(type, CH,SLOT1); FM_KEYOFF(CH, SLOT1); } if (!CH->SLOT[SLOT2].key) { FM_KEYON(type, CH,SLOT2); FM_KEYOFF(CH, SLOT2); } if (!CH->SLOT[SLOT3].key) { FM_KEYON(type, CH,SLOT3); FM_KEYOFF(CH, SLOT3); } if (!CH->SLOT[SLOT4].key) { FM_KEYON(type, CH,SLOT4); FM_KEYOFF(CH, SLOT4); } } #ifdef __STATE_H__ /* FM channel save , internal state only */ static void FMsave_state_channel(const device_config *device,FM_CH *CH,int num_ch) { int slot , ch; for(ch=0;chop1_out); state_save_register_device_item(device, ch, CH->fc); /* slots */ for(slot=0;slot<4;slot++) { FM_SLOT *SLOT = &CH->SLOT[slot]; state_save_register_device_item(device, ch * 4 + slot, SLOT->phase); state_save_register_device_item(device, ch * 4 + slot, SLOT->state); state_save_register_device_item(device, ch * 4 + slot, SLOT->volume); } } } static void FMsave_state_st(const device_config *device,FM_ST *ST) { #if FM_BUSY_FLAG_SUPPORT state_save_register_device_item(device, 0, ST->busy_expiry_time.seconds ); state_save_register_device_item(device, 0, ST->busy_expiry_time.attoseconds ); #endif state_save_register_device_item(device, 0, ST->address ); state_save_register_device_item(device, 0, ST->irq ); state_save_register_device_item(device, 0, ST->irqmask ); state_save_register_device_item(device, 0, ST->status ); state_save_register_device_item(device, 0, ST->mode ); state_save_register_device_item(device, 0, ST->prescaler_sel ); state_save_register_device_item(device, 0, ST->fn_h ); state_save_register_device_item(device, 0, ST->TA ); state_save_register_device_item(device, 0, ST->TAC ); state_save_register_device_item(device, 0, ST->TB ); state_save_register_device_item(device, 0, ST->TBC ); } #endif /* _STATE_H */ #if BUILD_OPN /* prescaler set (and make time tables) */ static void OPNSetPres(FM_OPN *OPN, int pres, int timer_prescaler, int SSGpres) { int i; /* frequency base */ OPN->ST.freqbase = (OPN->ST.rate) ? ((double)OPN->ST.clock / OPN->ST.rate) / pres : 0; #if 0 OPN->ST.rate = (double)OPN->ST.clock / pres; OPN->ST.freqbase = 1.0; #endif //OPN->eg_timer_add = (1<ST.freqbase; OPN->eg_timer_add = (UINT32)((1 << EG_SH) * OPN->ST.freqbase); OPN->eg_timer_overflow = ( 3 ) * (1<ST.timer_prescaler = timer_prescaler; /* SSG part prescaler set */ if( SSGpres ) (*OPN->ST.SSG->set_clock)( OPN->ST.param, OPN->ST.clock * 2 / SSGpres ); /* make time tables */ init_timetables( &OPN->ST, dt_tab ); /* there are 2048 FNUMs that can be generated using FNUM/BLK registers but LFO works with one more bit of a precision so we really need 4096 elements */ /* calculate fnumber -> increment counter table */ for(i = 0; i < 4096; i++) { /* freq table for octave 7 */ /* OPN phase increment counter = 20bit */ OPN->fn_table[i] = (UINT32)( (double)i * 32 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ #if 0 logerror("FM.C: fn_table[%4i] = %08x (dec=%8i)\n", i, OPN->fn_table[i]>>6,OPN->fn_table[i]>>6 ); #endif } /* maximal frequency is required for Phase overflow calculation, register size is 17 bits (Nemesis) */ OPN->fn_max = (UINT32)( (double)0x20000 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); /* LFO freq. table */ for(i = 0; i < 8; i++) { /* Amplitude modulation: 64 output levels (triangle waveform); 1 level lasts for one of "lfo_samples_per_step" samples */ /* Phase modulation: one entry from lfo_pm_output lasts for one of 4 * "lfo_samples_per_step" samples */ //OPN->lfo_freq[i] = (1.0 / lfo_samples_per_step[i]) * (1<ST.freqbase; OPN->lfo_freq[i] = (UINT32)((1.0 / lfo_samples_per_step[i]) * (1 << LFO_SH) * OPN->ST.freqbase); #if 0 logerror("FM.C: lfo_freq[%i] = %08x (dec=%8i)\n", i, OPN->lfo_freq[i],OPN->lfo_freq[i] ); #endif } } /* write a OPN mode register 0x20-0x2f */ static void OPNWriteMode(FM_OPN *OPN, int r, int v) { UINT8 c; FM_CH *CH; switch(r) { case 0x21: /* Test */ break; case 0x22: /* LFO FREQ (YM2608/YM2610/YM2610B/YM2612) */ if( OPN->type & TYPE_LFOPAN ) { if (v&0x08) /* LFO enabled ? */ { OPN->lfo_inc = OPN->lfo_freq[v&7]; } else { OPN->lfo_inc = 0; } } break; case 0x24: /* timer A High 8*/ OPN->ST.TA = (OPN->ST.TA & 0x03)|(((int)v)<<2); break; case 0x25: /* timer A Low 2*/ OPN->ST.TA = (OPN->ST.TA & 0x3fc)|(v&3); break; case 0x26: /* timer B */ OPN->ST.TB = v; break; case 0x27: /* mode, timer control */ set_timers( &(OPN->ST),OPN->ST.param,v ); break; case 0x28: /* key on / off */ c = v & 0x03; if( c == 3 ) break; if( (v&0x04) && (OPN->type & TYPE_6CH) ) c+=3; CH = OPN->P_CH; CH = &CH[c]; if(v&0x10) FM_KEYON(OPN->type,CH,SLOT1); else FM_KEYOFF(CH,SLOT1); if(v&0x20) FM_KEYON(OPN->type,CH,SLOT2); else FM_KEYOFF(CH,SLOT2); if(v&0x40) FM_KEYON(OPN->type,CH,SLOT3); else FM_KEYOFF(CH,SLOT3); if(v&0x80) FM_KEYON(OPN->type,CH,SLOT4); else FM_KEYOFF(CH,SLOT4); break; } } /* write a OPN register (0x30-0xff) */ static void OPNWriteReg(FM_OPN *OPN, int r, int v) { FM_CH *CH; FM_SLOT *SLOT; UINT8 c = OPN_CHAN(r); if (c == 3) return; /* 0xX3,0xX7,0xXB,0xXF */ if (r >= 0x100) c+=3; CH = OPN->P_CH; CH = &CH[c]; SLOT = &(CH->SLOT[OPN_SLOT(r)]); switch( r & 0xf0 ) { case 0x30: /* DET , MUL */ set_det_mul(&OPN->ST,CH,SLOT,v); break; case 0x40: /* TL */ set_tl(CH,SLOT,v); break; case 0x50: /* KS, AR */ set_ar_ksr(OPN->type,CH,SLOT,v); break; case 0x60: /* bit7 = AM ENABLE, DR */ set_dr(OPN->type, SLOT,v); if(OPN->type & TYPE_LFOPAN) /* YM2608/2610/2610B/2612 */ { SLOT->AMmask = (v&0x80) ? ~0 : 0; } break; case 0x70: /* SR */ set_sr(OPN->type,SLOT,v); break; case 0x80: /* SL, RR */ set_sl_rr(OPN->type,SLOT,v); break; case 0x90: /* SSG-EG */ SLOT->ssg = v&0x0f; SLOT->ssgn = (v&0x04)>>1; /* bit 1 in ssgn = attack */ /* SSG-EG envelope shapes : E AtAlH 1 0 0 0 \\\\ 1 0 0 1 \___ 1 0 1 0 \/\/ ___ 1 0 1 1 \ 1 1 0 0 //// ___ 1 1 0 1 / 1 1 1 0 /\/\ 1 1 1 1 /___ E = SSG-EG enable The shapes are generated using Attack, Decay and Sustain phases. Each single character in the diagrams above represents this whole sequence: - when KEY-ON = 1, normal Attack phase is generated (*without* any difference when compared to normal mode), - later, when envelope level reaches minimum level (max volume), the EG switches to Decay phase (which works with bigger steps when compared to normal mode - see below), - later when envelope level passes the SL level, the EG swithes to Sustain phase (which works with bigger steps when compared to normal mode - see below), - finally when envelope level reaches maximum level (min volume), the EG switches to Attack phase again (depends on actual waveform). Important is that when switch to Attack phase occurs, the phase counter of that operator will be zeroed-out (as in normal KEY-ON) but not always. (I havent found the rule for that - perhaps only when the output level is low) The difference (when compared to normal Envelope Generator mode) is that the resolution in Decay and Sustain phases is 4 times lower; this results in only 256 steps instead of normal 1024. In other words: when SSG-EG is disabled, the step inside of the EG is one, when SSG-EG is enabled, the step is four (in Decay and Sustain phases). Times between the level changes are the same in both modes. Important: Decay 1 Level (so called SL) is compared to actual SSG-EG output, so it is the same in both SSG and no-SSG modes, with this exception: when the SSG-EG is enabled and is generating raising levels (when the EG output is inverted) the SL will be found at wrong level !!! For example, when SL=02: 0 -6 = -6dB in non-inverted EG output 96-6 = -90dB in inverted EG output Which means that EG compares its level to SL as usual, and that the output is simply inverted afterall. The Yamaha's manuals say that AR should be set to 0x1f (max speed). That is not necessary, but then EG will be generating Attack phase. */ break; case 0xa0: switch( OPN_SLOT(r) ) { case 0: /* 0xa0-0xa2 : FNUM1 */ { UINT32 fn = (((UINT32)( (OPN->ST.fn_h)&7))<<8) + v; UINT8 blk = OPN->ST.fn_h>>3; /* keyscale code */ CH->kcode = (blk<<2) | opn_fktable[fn >> 7]; /* phase increment counter */ CH->fc = OPN->fn_table[fn*2]>>(7-blk); /* store fnum in clear form for LFO PM calculations */ CH->block_fnum = (blk<<11) | fn; CH->SLOT[SLOT1].Incr=-1; } break; case 1: /* 0xa4-0xa6 : FNUM2,BLK */ OPN->ST.fn_h = v&0x3f; break; case 2: /* 0xa8-0xaa : 3CH FNUM1 */ if(r < 0x100) { UINT32 fn = (((UINT32)(OPN->SL3.fn_h&7))<<8) + v; UINT8 blk = OPN->SL3.fn_h>>3; /* keyscale code */ OPN->SL3.kcode[c]= (blk<<2) | opn_fktable[fn >> 7]; /* phase increment counter */ OPN->SL3.fc[c] = OPN->fn_table[fn*2]>>(7-blk); OPN->SL3.block_fnum[c] = (blk<<11) | fn; (OPN->P_CH)[2].SLOT[SLOT1].Incr=-1; } break; case 3: /* 0xac-0xae : 3CH FNUM2,BLK */ if(r < 0x100) OPN->SL3.fn_h = v&0x3f; break; } break; case 0xb0: switch( OPN_SLOT(r) ) { case 0: /* 0xb0-0xb2 : FB,ALGO */ { int feedback = (v>>3)&7; CH->ALGO = v&7; CH->FB = feedback ? feedback+6 : 0; setup_connection( OPN, CH, c ); } break; case 1: /* 0xb4-0xb6 : L , R , AMS , PMS (YM2612/YM2610B/YM2610/YM2608) */ if( OPN->type & TYPE_LFOPAN) { /* b0-2 PMS */ CH->pms = (v & 7) * 32; /* CH->pms = PM depth * 32 (index in lfo_pm_table) */ /* b4-5 AMS */ CH->ams = lfo_ams_depth_shift[(v>>4) & 0x03]; /* PAN : b7 = L, b6 = R */ OPN->pan[ c*2 ] = (v & 0x80) ? ~0 : 0; OPN->pan[ c*2+1 ] = (v & 0x40) ? ~0 : 0; } break; } break; } } #endif /* BUILD_OPN */ #if BUILD_OPN_PRESCALER /* prescaler circuit (best guess to verified chip behaviour) +--------------+ +-sel2-+ | +--|in20 | +---+ | +-sel1-+ | | M-CLK -+-|1/2|-+--|in10 | +---+ | out|--INT_CLOCK | +---+ | out|-|1/3|-|in21 | +----------|in11 | +---+ +------+ +------+ reg.2d : sel2 = in21 (select sel2) reg.2e : sel1 = in11 (select sel1) reg.2f : sel1 = in10 , sel2 = in20 (clear selector) reset : sel1 = in11 , sel2 = in21 (clear both) */ static void OPNPrescaler_w(FM_OPN *OPN , int addr, int pre_divider) { static const int opn_pres[4] = { 2*12 , 2*12 , 6*12 , 3*12 }; static const int ssg_pres[4] = { 1 , 1 , 4 , 2 }; int sel; switch(addr) { case 0: /* when reset */ OPN->ST.prescaler_sel = 2; break; case 1: /* when postload */ break; case 0x2d: /* divider sel : select 1/1 for 1/3line */ OPN->ST.prescaler_sel |= 0x02; break; case 0x2e: /* divider sel , select 1/3line for output */ OPN->ST.prescaler_sel |= 0x01; break; case 0x2f: /* divider sel , clear both selector to 1/2,1/2 */ OPN->ST.prescaler_sel = 0; break; } sel = OPN->ST.prescaler_sel & 3; /* update prescaler */ OPNSetPres( OPN, opn_pres[sel]*pre_divider, opn_pres[sel]*pre_divider, ssg_pres[sel]*pre_divider ); } #endif /* BUILD_OPN_PRESCALER */ #if BUILD_YM2203 /*****************************************************************************/ /* YM2203 local section */ /*****************************************************************************/ /* here's the virtual YM2203(OPN) */ typedef struct { UINT8 REGS[256]; /* registers */ FM_OPN OPN; /* OPN state */ FM_CH CH[3]; /* channel state */ } YM2203; /* Generate samples for one of the YM2203s */ void ym2203_update_one(void *chip, FMSAMPLE **buffer, int length) { YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; int i; FMSAMPLE *bufL = buffer[0]; FMSAMPLE *bufR = buffer[1]; FM_CH *cch[3]; cch[0] = &F2203->CH[0]; cch[1] = &F2203->CH[1]; cch[2] = &F2203->CH[2]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (F2203->OPN.ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); /* YM2203 doesn't have LFO so we must keep these globals at 0 level */ OPN->LFO_AM = 0; OPN->LFO_PM = 0; /* buffering */ for (i=0; i < length ; i++) { /* clear outputs */ OPN->out_fm[0] = 0; OPN->out_fm[1] = 0; OPN->out_fm[2] = 0; /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); } /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); /* buffering */ { int lt; lt = OPN->out_fm[0] + OPN->out_fm[1] + OPN->out_fm[2]; lt >>= FINAL_SH; //Limit( lt , MAXOUT, MINOUT ); #ifdef SAVE_SAMPLE SAVE_ALL_CHANNELS #endif /* buffering */ bufL[i] = lt; bufR[i] = lt; } /* timer A control */ INTERNAL_TIMER_A( &F2203->OPN.ST , cch[2] ) } INTERNAL_TIMER_B(&F2203->OPN.ST,length) } /* ---------- reset one of chip ---------- */ void ym2203_reset_chip(void *chip) { int i; YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; /* Reset Prescaler */ OPNPrescaler_w(OPN, 0 , 1 ); /* reset SSG section */ (*OPN->ST.SSG->reset)(OPN->ST.param); /* status clear */ FM_IRQMASK_SET(&OPN->ST,0x03); FM_BUSY_CLEAR(&OPN->ST); OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ OPN->eg_timer = 0; OPN->eg_cnt = 0; FM_STATUS_RESET(&OPN->ST, 0xff); reset_channels( &OPN->ST , F2203->CH , 3 ); /* reset OPerator paramater */ for(i = 0xb2 ; i >= 0x30 ; i-- ) OPNWriteReg(OPN,i,0); for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); } #ifdef __STATE_H__ void ym2203_postload(void *chip) { if (chip) { YM2203 *F2203 = (YM2203 *)chip; int r; /* prescaler */ OPNPrescaler_w(&F2203->OPN,1,1); /* SSG registers */ for(r=0;r<16;r++) { (*F2203->OPN.ST.SSG->write)(F2203->OPN.ST.param,0,r); (*F2203->OPN.ST.SSG->write)(F2203->OPN.ST.param,1,F2203->REGS[r]); } /* OPN registers */ /* DT / MULTI , TL , KS / AR , AMON / DR , SR , SL / RR , SSG-EG */ for(r=0x30;r<0x9e;r++) if((r&3) != 3) OPNWriteReg(&F2203->OPN,r,F2203->REGS[r]); /* FB / CONNECT , L / R / AMS / PMS */ for(r=0xb0;r<0xb6;r++) if((r&3) != 3) OPNWriteReg(&F2203->OPN,r,F2203->REGS[r]); /* channels */ /*FM_channel_postload(F2203->CH,3);*/ } } static void YM2203_save_state(YM2203 *F2203, const device_config *device) { state_save_register_device_item_array(device, 0, F2203->REGS); FMsave_state_st(device,&F2203->OPN.ST); FMsave_state_channel(device,F2203->CH,3); /* 3slots */ state_save_register_device_item_array (device, 0, F2203->OPN.SL3.fc); state_save_register_device_item (device, 0, F2203->OPN.SL3.fn_h); state_save_register_device_item_array (device, 0, F2203->OPN.SL3.kcode); } #endif /* _STATE_H */ /* ---------- Initialize YM2203 emulator(s) ---------- 'num' is the number of virtual YM2203s to allocate 'clock' is the chip clock in Hz 'rate' is sampling rate */ //void * ym2203_init(void *param, const device_config *device, int clock, int rate, // FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) void * ym2203_init(void *param, int clock, int rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) { YM2203 *F2203; /* allocate ym2203 state space */ if( (F2203 = (YM2203 *)malloc(sizeof(YM2203)))==NULL) return NULL; /* clear */ memset(F2203,0,sizeof(YM2203)); if( !init_tables() ) { free( F2203 ); return NULL; } F2203->OPN.ST.param = param; F2203->OPN.type = TYPE_YM2203; F2203->OPN.P_CH = F2203->CH; //F2203->OPN.ST.device = device; F2203->OPN.ST.clock = clock; F2203->OPN.ST.rate = rate; F2203->OPN.ST.timer_handler = timer_handler; F2203->OPN.ST.IRQ_Handler = IRQHandler; F2203->OPN.ST.SSG = ssg; #ifdef __STATE_H__ YM2203_save_state(F2203, device); #endif return F2203; } /* shut down emulator */ void ym2203_shutdown(void *chip) { YM2203 *FM2203 = (YM2203 *)chip; FMCloseTable(); free(FM2203); } /* YM2203 I/O interface */ int ym2203_write(void *chip,int a,UINT8 v) { YM2203 *F2203 = (YM2203 *)chip; FM_OPN *OPN = &F2203->OPN; if( !(a&1) ) { /* address port */ OPN->ST.address = (v &= 0xff); /* Write register to SSG emulator */ if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); /* prescaler select : 2d,2e,2f */ if( v >= 0x2d && v <= 0x2f ) OPNPrescaler_w(OPN , v , 1); } else { /* data port */ int addr = OPN->ST.address; F2203->REGS[addr] = v; switch( addr & 0xf0 ) { case 0x00: /* 0x00-0x0f : SSG section */ /* Write data to SSG emulator */ (*OPN->ST.SSG->write)(OPN->ST.param,a,v); break; case 0x20: /* 0x20-0x2f : Mode section */ ym2203_update_req(OPN->ST.param); /* write register */ OPNWriteMode(OPN,addr,v); break; default: /* 0x30-0xff : OPN section */ ym2203_update_req(OPN->ST.param); /* write register */ OPNWriteReg(OPN,addr,v); } FM_BUSY_SET(&OPN->ST,1); } return OPN->ST.irq; } UINT8 ym2203_read(void *chip,int a) { YM2203 *F2203 = (YM2203 *)chip; int addr = F2203->OPN.ST.address; UINT8 ret = 0; if( !(a&1) ) { /* status port */ ret = FM_STATUS_FLAG(&F2203->OPN.ST); } else { /* data port (only SSG) */ if( addr < 16 ) ret = (*F2203->OPN.ST.SSG->read)(F2203->OPN.ST.param); } return ret; } int ym2203_timer_over(void *chip,int c) { YM2203 *F2203 = (YM2203 *)chip; if( c ) { /* Timer B */ TimerBOver( &(F2203->OPN.ST) ); } else { /* Timer A */ ym2203_update_req(F2203->OPN.ST.param); /* timer update */ TimerAOver( &(F2203->OPN.ST) ); /* CSM mode key,TL control */ if( F2203->OPN.ST.mode & 0x80 ) { /* CSM mode auto key on */ CSMKeyControll( F2203->OPN.type, &(F2203->CH[2]) ); } } return F2203->OPN.ST.irq; } void ym2203_set_mutemask(void *chip, UINT32 MuteMask) { YM2203 *F2203 = (YM2203 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 3; CurChn ++) F2203->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; return; } #endif /* BUILD_YM2203 */ #if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) /* ADPCM type A channel struct */ typedef struct { UINT8 flag; /* port state */ UINT8 flagMask; /* arrived flag mask */ UINT8 now_data; /* current ROM data */ UINT32 now_addr; /* current ROM address */ UINT32 now_step; UINT32 step; UINT32 start; /* sample data start address*/ UINT32 end; /* sample data end address */ UINT8 IL; /* Instrument Level */ INT32 adpcm_acc; /* accumulator */ INT32 adpcm_step; /* step */ INT32 adpcm_out; /* (speedup) hiro-shi!! */ INT8 vol_mul; /* volume in "0.75dB" steps */ UINT8 vol_shift; /* volume in "-6dB" steps */ INT32 *pan; /* &out_adpcm[OPN_xxxx] */ UINT8 Muted; } ADPCM_CH; /* here's the virtual YM2610 */ typedef struct { UINT8 REGS[512]; /* registers */ FM_OPN OPN; /* OPN state */ FM_CH CH[6]; /* channel state */ UINT8 addr_A1; /* address line A1 */ /* ADPCM-A unit */ //const UINT8 *pcmbuf; /* pcm rom buffer */ UINT8 *pcmbuf; /* pcm rom buffer */ UINT32 pcm_size; /* size of pcm rom */ UINT8 adpcmTL; /* adpcmA total level */ ADPCM_CH adpcm[6]; /* adpcm channels */ UINT32 adpcmreg[0x30]; /* registers */ UINT8 adpcm_arrivedEndAddress; YM_DELTAT deltaT; /* Delta-T ADPCM unit */ UINT8 MuteDeltaT; UINT8 flagmask; /* YM2608 only */ UINT8 irqmask; /* YM2608 only */ } YM2610; /* here is the virtual YM2608 */ typedef YM2610 YM2608; /**** YM2610 ADPCM defines ****/ #define ADPCM_SHIFT (16) /* frequency step rate */ #define ADPCMA_ADDRESS_SHIFT 8 /* adpcm A address shift */ /* Algorithm and tables verified on real YM2608 and YM2610 */ /* usual ADPCM table (16 * 1.1^N) */ static const int steps[49] = { 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 }; /* different from the usual ADPCM table */ static const int step_inc[8] = { -1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16 }; /* speedup purposes only */ static int jedi_table[ 49*16 ]; static void Init_ADPCMATable(void) { int step, nib; for (step = 0; step < 49; step++) { /* loop over all nibbles and compute the difference */ for (nib = 0; nib < 16; nib++) { int value = (2*(nib & 0x07) + 1) * steps[step] / 8; jedi_table[step*16 + nib] = (nib&0x08) ? -value : value; } } } /* ADPCM A (Non control type) : calculate one channel output */ INLINE void ADPCMA_calc_chan( YM2610 *F2610, ADPCM_CH *ch ) { UINT32 step; UINT8 data; if (ch->Muted) return; ch->now_step += ch->step; if ( ch->now_step >= (1<now_step >> ADPCM_SHIFT; ch->now_step &= (1< instead of == */ /* YM2610 checks lower 20 bits only, the 4 MSB bits are sample bank */ /* Here we use 1<<21 to compensate for nibble calculations */ if ( (ch->now_addr & ((1<<21)-1)) == ((ch->end<<1) & ((1<<21)-1)) ) { ch->flag = 0; F2610->adpcm_arrivedEndAddress |= ch->flagMask; return; } #if 0 if ( ch->now_addr > (F2610->pcmsizeA<<1) ) { #ifdef _DEBUG LOG(LOG_WAR,("YM2610: Attempting to play past adpcm rom size!\n" )); #endif return; } #endif if ( ch->now_addr&1 ) data = ch->now_data & 0x0f; else { ch->now_data = *(F2610->pcmbuf+(ch->now_addr>>1)); data = (ch->now_data >> 4) & 0x0f; } ch->now_addr++; ch->adpcm_acc += jedi_table[ch->adpcm_step + data]; /* the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205), it does not saturate (like the msm5218) */ ch->adpcm_acc &= 0xfff; /* extend 12-bit signed int */ if (ch->adpcm_acc & 0x800) ch->adpcm_acc |= ~0xfff; ch->adpcm_step += step_inc[data & 7]; Limit( ch->adpcm_step, 48*16, 0*16 ); }while(--step); /* calc pcm * volume data */ ch->adpcm_out = ((ch->adpcm_acc * ch->vol_mul) >> ch->vol_shift) & ~3; /* multiply, shift and mask out 2 LSB bits */ } /* output for work of output channels (out_adpcm[OPNxxxx])*/ *(ch->pan) += ch->adpcm_out; } /* ADPCM type A Write */ static void FM_ADPCMAWrite(YM2610 *F2610,int r,int v) { ADPCM_CH *adpcm = F2610->adpcm; UINT8 c = r&0x07; F2610->adpcmreg[r] = v&0xff; /* stock data */ switch( r ) { case 0x00: /* DM,--,C5,C4,C3,C2,C1,C0 */ if( !(v&0x80) ) { /* KEY ON */ for( c = 0; c < 6; c++ ) { if( (v>>c)&1 ) { /**** start adpcm ****/ // The .step variable is already set and for the YM2608 it is different on channels 4 and 5. //adpcm[c].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); adpcm[c].now_addr = adpcm[c].start<<1; adpcm[c].now_step = 0; adpcm[c].adpcm_acc = 0; adpcm[c].adpcm_step= 0; adpcm[c].adpcm_out = 0; adpcm[c].flag = 1; if(F2610->pcmbuf==NULL) { /* Check ROM Mapped */ #ifdef _DEBUG logerror("YM2608-YM2610: ADPCM-A rom not mapped\n"); #endif adpcm[c].flag = 0; } else { if(adpcm[c].end >= F2610->pcm_size) { /* Check End in Range */ #ifdef _DEBUG logerror("YM2610: ADPCM-A end out of range: $%08x\n",adpcm[c].end); #endif /*adpcm[c].end = F2610->pcm_size-1;*/ /* JB: DO NOT uncomment this, otherwise you will break the comparison in the ADPCM_CALC_CHA() */ } if(adpcm[c].start >= F2610->pcm_size) /* Check Start in Range */ { #ifdef _DEBUG logerror("YM2608-YM2610: ADPCM-A start out of range: $%08x\n",adpcm[c].start); #endif adpcm[c].flag = 0; } } } } } else { /* KEY OFF */ for( c = 0; c < 6; c++ ) if( (v>>c)&1 ) adpcm[c].flag = 0; } break; case 0x01: /* B0-5 = TL */ F2610->adpcmTL = (v & 0x3f) ^ 0x3f; for( c = 0; c < 6; c++ ) { int volume = F2610->adpcmTL + adpcm[c].IL; if ( volume >= 63 ) /* This is correct, 63 = quiet */ { adpcm[c].vol_mul = 0; adpcm[c].vol_shift = 0; } else { adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ adpcm[c].vol_shift = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } /* calc pcm * volume data */ adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ } break; default: c = r&0x07; if( c >= 0x06 ) return; switch( r&0x38 ) { case 0x08: /* B7=L,B6=R, B4-0=IL */ { int volume; adpcm[c].IL = (v & 0x1f) ^ 0x1f; volume = F2610->adpcmTL + adpcm[c].IL; if ( volume >= 63 ) /* This is correct, 63 = quiet */ { adpcm[c].vol_mul = 0; adpcm[c].vol_shift = 0; } else { adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ adpcm[c].vol_shift = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } adpcm[c].pan = &F2610->OPN.out_adpcm[(v>>6)&0x03]; /* calc pcm * volume data */ adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ } break; case 0x10: case 0x18: adpcm[c].start = ( (F2610->adpcmreg[0x18 + c]*0x0100 | F2610->adpcmreg[0x10 + c]) << ADPCMA_ADDRESS_SHIFT); break; case 0x20: case 0x28: adpcm[c].end = ( (F2610->adpcmreg[0x28 + c]*0x0100 | F2610->adpcmreg[0x20 + c]) << ADPCMA_ADDRESS_SHIFT); adpcm[c].end += (1<flag); state_save_register_device_item(device, ch, adpcm->now_data); state_save_register_device_item(device, ch, adpcm->now_addr); state_save_register_device_item(device, ch, adpcm->now_step); state_save_register_device_item(device, ch, adpcm->adpcm_acc); state_save_register_device_item(device, ch, adpcm->adpcm_step); state_save_register_device_item(device, ch, adpcm->adpcm_out); } } #endif /* _STATE_H */ #endif /* (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) */ #if BUILD_YM2608 /*****************************************************************************/ /* YM2608 local section */ /*****************************************************************************/ const unsigned int YM2608_ADPCM_ROM_addr[2*6] = { 0x0000, 0x01bf, /* bass drum */ 0x01c0, 0x043f, /* snare drum */ 0x0440, 0x1b7f, /* top cymbal */ 0x1b80, 0x1cff, /* high hat */ 0x1d00, 0x1f7f, /* tom tom */ 0x1f80, 0x1fff /* rim shot */ }; /* This data is derived from the chip's output - internal ROM can't be read. It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal. */ const unsigned char YM2608_ADPCM_ROM[0x2000] = { /* Source: 01BD.ROM */ /* Length: 448 / 0x000001C0 */ 0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54, 0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97, 0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0, 0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1, 0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24, 0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99, 0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99, 0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09, 0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8, 0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39, 0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9, 0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A, 0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40, 0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00, 0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12, 0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18, 0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52, 0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02, 0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B, 0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC, 0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A, 0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98, 0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A, 0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99, 0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99, 0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11, 0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31, 0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80, /* Source: 02SD.ROM */ /* Length: 640 / 0x00000280 */ 0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1, 0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41, 0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03, 0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18, 0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02, 0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92, 0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28, 0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62, 0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E, 0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09, 0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB, 0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83, 0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85, 0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21, 0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0, 0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80, 0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21, 0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8, 0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F, 0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A, 0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12, 0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36, 0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80, 0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90, 0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99, 0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03, 0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89, 0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18, 0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27, 0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9, 0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39, 0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23, 0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1, 0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3, 0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9, 0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71, 0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A, 0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88, 0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C, 0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10, /* Source: 04TOP.ROM */ /* Length: 5952 / 0x00001740 */ 0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C, 0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00, 0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E, 0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C, 0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38, 0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81, 0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A, 0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81, 0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4, 0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8, 0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10, 0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29, 0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98, 0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59, 0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3, 0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28, 0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B, 0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B, 0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11, 0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0, 0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90, 0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00, 0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8, 0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29, 0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F, 0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12, 0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8, 0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4, 0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82, 0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11, 0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09, 0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A, 0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3, 0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85, 0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88, 0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80, 0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C, 0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10, 0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81, 0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81, 0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8, 0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A, 0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79, 0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49, 0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA, 0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99, 0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24, 0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7, 0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8, 0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0, 0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15, 0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18, 0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B, 0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C, 0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B, 0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7, 0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14, 0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02, 0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98, 0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88, 0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01, 0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2, 0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D, 0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19, 0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28, 0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21, 0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A, 0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2, 0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87, 0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11, 0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C, 0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A, 0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0, 0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1, 0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11, 0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90, 0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00, 0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2, 0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3, 0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82, 0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00, 0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B, 0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28, 0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79, 0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90, 0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1, 0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05, 0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10, 0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19, 0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D, 0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5, 0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90, 0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49, 0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C, 0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C, 0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0, 0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3, 0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4, 0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4, 0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9, 0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01, 0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88, 0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12, 0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D, 0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02, 0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09, 0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89, 0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A, 0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2, 0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49, 0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50, 0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80, 0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C, 0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C, 0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13, 0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01, 0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18, 0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C, 0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1, 0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3, 0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B, 0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E, 0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90, 0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28, 0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28, 0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E, 0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91, 0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00, 0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71, 0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20, 0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8, 0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03, 0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7, 0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C, 0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09, 0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12, 0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2, 0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6, 0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02, 0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B, 0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC, 0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0, 0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00, 0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A, 0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2, 0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3, 0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F, 0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A, 0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0, 0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84, 0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10, 0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21, 0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F, 0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B, 0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24, 0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3, 0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00, 0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93, 0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10, 0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30, 0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0, 0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7, 0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1, 0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19, 0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B, 0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99, 0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A, 0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C, 0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03, 0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1, 0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A, 0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19, 0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8, 0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11, 0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A, 0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59, 0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F, 0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C, 0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80, 0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5, 0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08, 0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29, 0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1, 0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28, 0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D, 0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A, 0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D, 0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48, 0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A, 0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3, 0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1, 0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18, 0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C, 0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A, 0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19, 0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91, 0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04, 0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29, 0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10, 0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1, 0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0, 0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84, 0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95, 0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11, 0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B, 0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19, 0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10, 0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1, 0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17, 0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A, 0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08, 0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C, 0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1, 0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2, 0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09, 0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20, 0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A, 0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D, 0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82, 0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1, 0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2, 0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99, 0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18, 0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1, 0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1, 0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01, 0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40, 0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A, 0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C, 0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83, 0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12, 0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09, 0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68, 0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92, 0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A, 0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80, 0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82, 0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80, 0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B, 0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B, 0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18, 0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91, 0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1, 0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6, 0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B, 0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89, 0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90, 0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A, 0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01, 0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98, 0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29, 0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D, 0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92, 0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93, 0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88, 0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79, 0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20, 0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59, 0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9, 0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8, 0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07, 0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13, 0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80, 0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB, 0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A, 0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20, 0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0, 0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20, 0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39, 0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92, 0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1, 0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0, 0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41, 0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC, 0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30, 0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18, 0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B, 0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A, 0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09, 0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10, 0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1, 0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96, 0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09, 0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09, 0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A, 0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B, 0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90, 0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0, 0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1, 0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28, 0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18, 0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF, 0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0, 0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02, 0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08, 0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96, 0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88, 0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28, 0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D, 0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0, 0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83, 0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3, 0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04, 0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2, 0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21, 0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98, 0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8, 0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23, 0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39, 0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08, 0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E, 0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14, 0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4, 0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0, 0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88, 0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0, 0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69, 0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08, 0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13, 0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0, 0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA, 0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81, 0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01, 0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B, 0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B, 0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4, 0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18, 0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01, 0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0, 0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5, 0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94, 0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A, 0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A, 0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88, 0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B, 0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01, 0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12, 0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12, 0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D, 0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02, 0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1, 0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82, 0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1, 0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A, 0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A, 0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F, 0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98, 0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1, 0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3, 0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68, 0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91, 0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A, 0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C, 0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90, 0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6, 0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89, 0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B, 0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA, 0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1, 0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93, 0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93, 0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00, 0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B, 0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2, 0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92, 0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89, 0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48, 0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10, 0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29, 0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91, 0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3, 0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90, /* Source: 08HH.ROM */ /* Length: 384 / 0x00000180 */ 0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3, 0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7, 0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D, 0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80, 0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B, 0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2, 0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91, 0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D, 0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C, 0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5, 0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B, 0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01, 0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92, 0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93, 0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4, 0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3, 0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08, 0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3, 0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88, 0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92, 0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00, 0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4, 0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4, 0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0, /* Source: 10TOM.ROM */ /* Length: 640 / 0x00000280 */ 0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2, 0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11, 0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1, 0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32, 0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9, 0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01, 0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14, 0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA, 0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81, 0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00, 0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63, 0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB, 0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34, 0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB, 0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31, 0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99, 0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A, 0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30, 0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02, 0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98, 0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11, 0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11, 0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A, 0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35, 0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11, 0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB, 0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42, 0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99, 0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20, 0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81, 0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31, 0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC, 0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33, 0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB, 0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31, 0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99, 0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99, 0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88, 0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12, 0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* Source: 20RIM.ROM */ /* Length: 128 / 0x00000080 */ 0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A, 0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2, 0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C, 0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91, 0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59, 0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92, 0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6, 0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80, }; /* flag enable control 0x110 */ INLINE void YM2608IRQFlagWrite(FM_OPN *OPN, YM2608 *F2608, int v) { if( v & 0x80 ) { /* Reset IRQ flag */ FM_STATUS_RESET(&OPN->ST, 0xf7); /* don't touch BUFRDY flag otherwise we'd have to call ymdeltat module to set the flag back */ } else { /* Set status flag mask */ F2608->flagmask = (~(v&0x1f)); FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); } } /* compatible mode & IRQ enable control 0x29 */ INLINE void YM2608IRQMaskWrite(FM_OPN *OPN, YM2608 *F2608, int v) { /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ /* extend 3ch. enable/disable */ if(v&0x80) OPN->type |= TYPE_6CH; /* OPNA mode - 6 FM channels */ else OPN->type &= ~TYPE_6CH; /* OPN mode - 3 FM channels */ /* IRQ MASK store and set */ F2608->irqmask = v&0x1f; FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); } /* Generate samples for one of the YM2608s */ void ym2608_update_one(void *chip, FMSAMPLE **buffer, int length) { YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; YM_DELTAT *DELTAT = &F2608->deltaT; int i,j; FMSAMPLE *bufL,*bufR; FM_CH *cch[6]; INT32 *out_fm = OPN->out_fm; /* set bufer */ bufL = buffer[0]; bufR = buffer[1]; cch[0] = &F2608->CH[0]; cch[1] = &F2608->CH[1]; cch[2] = &F2608->CH[2]; cch[3] = &F2608->CH[3]; cch[4] = &F2608->CH[4]; cch[5] = &F2608->CH[5]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); refresh_fc_eg_chan( OPN, cch[4] ); refresh_fc_eg_chan( OPN, cch[5] ); /* buffering */ for(i=0; i < length ; i++) { advance_lfo(OPN); /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[0] = 0; out_fm[1] = 0; out_fm[2] = 0; out_fm[3] = 0; out_fm[4] = 0; out_fm[5] = 0; /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); chan_calc(OPN, cch[3], 3 ); chan_calc(OPN, cch[4], 4 ); chan_calc(OPN, cch[5], 5 ); /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2608->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2608->adpcm[j].flag ) ADPCMA_calc_chan( F2608, &F2608->adpcm[j]); } /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); } /* buffering */ { int lt,rt; /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; lt += ((out_fm[0]>>1) & OPN->pan[0]); // shift right verified on real YM2608 rt += ((out_fm[0]>>1) & OPN->pan[1]); lt += ((out_fm[1]>>1) & OPN->pan[2]); rt += ((out_fm[1]>>1) & OPN->pan[3]); lt += ((out_fm[2]>>1) & OPN->pan[4]); rt += ((out_fm[2]>>1) & OPN->pan[5]); lt += ((out_fm[3]>>1) & OPN->pan[6]); rt += ((out_fm[3]>>1) & OPN->pan[7]); lt += ((out_fm[4]>>1) & OPN->pan[8]); rt += ((out_fm[4]>>1) & OPN->pan[9]); lt += ((out_fm[5]>>1) & OPN->pan[10]); rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ // this way it's louder (and more accurate) lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[0] & OPN->pan[0]); rt += (out_fm[0] & OPN->pan[1]); lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[3] & OPN->pan[6]); rt += (out_fm[3] & OPN->pan[7]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); lt >>= FINAL_SH; rt >>= FINAL_SH; //Limit( lt, MAXOUT, MINOUT ); //Limit( rt, MAXOUT, MINOUT ); /* buffering */ bufL[i] = lt; bufR[i] = rt; #ifdef SAVE_SAMPLE SAVE_ALL_CHANNELS #endif } /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) } INTERNAL_TIMER_B(&OPN->ST,length) /* check IRQ for DELTA-T EOS */ FM_STATUS_SET(&OPN->ST, 0); } #ifdef __STATE_H__ void ym2608_postload(void *chip) { if (chip) { YM2608 *F2608 = (YM2608 *)chip; int r; /* prescaler */ OPNPrescaler_w(&F2608->OPN,1,2); F2608->deltaT.freqbase = F2608->OPN.ST.freqbase; /* IRQ mask / mode */ YM2608IRQMaskWrite(&F2608->OPN, F2608, F2608->REGS[0x29]); /* SSG registers */ for(r=0;r<16;r++) { (*F2608->OPN.ST.SSG->write)(F2608->OPN.ST.param,0,r); (*F2608->OPN.ST.SSG->write)(F2608->OPN.ST.param,1,F2608->REGS[r]); } /* OPN registers */ /* DT / MULTI , TL , KS / AR , AMON / DR , SR , SL / RR , SSG-EG */ for(r=0x30;r<0x9e;r++) if((r&3) != 3) { OPNWriteReg(&F2608->OPN,r,F2608->REGS[r]); OPNWriteReg(&F2608->OPN,r|0x100,F2608->REGS[r|0x100]); } /* FB / CONNECT , L / R / AMS / PMS */ for(r=0xb0;r<0xb6;r++) if((r&3) != 3) { OPNWriteReg(&F2608->OPN,r,F2608->REGS[r]); OPNWriteReg(&F2608->OPN,r|0x100,F2608->REGS[r|0x100]); } /* FM channels */ /*FM_channel_postload(F2608->CH,6);*/ /* rhythm(ADPCMA) */ FM_ADPCMAWrite(F2608,1,F2608->REGS[0x111]); for( r=0x08 ; r<0x0c ; r++) FM_ADPCMAWrite(F2608,r,F2608->REGS[r+0x110]); /* Delta-T ADPCM unit */ YM_DELTAT_postload(&F2608->deltaT , &F2608->REGS[0x100] ); } } static void YM2608_save_state(YM2608 *F2608, const device_config *device) { state_save_register_device_item_array(device, 0, F2608->REGS); FMsave_state_st(device,&F2608->OPN.ST); FMsave_state_channel(device,F2608->CH,6); /* 3slots */ state_save_register_device_item_array(device, 0, F2608->OPN.SL3.fc); state_save_register_device_item(device, 0, F2608->OPN.SL3.fn_h); state_save_register_device_item_array(device, 0, F2608->OPN.SL3.kcode); /* address register1 */ state_save_register_device_item(device, 0, F2608->addr_A1); /* rythm(ADPCMA) */ FMsave_state_adpcma(device,F2608->adpcm); /* Delta-T ADPCM unit */ YM_DELTAT_savestate(device,&F2608->deltaT); } #endif /* _STATE_H */ static void YM2608_deltat_status_set(void *chip, UINT8 changebits) { YM2608 *F2608 = (YM2608 *)chip; FM_STATUS_SET(&(F2608->OPN.ST), changebits); } static void YM2608_deltat_status_reset(void *chip, UINT8 changebits) { YM2608 *F2608 = (YM2608 *)chip; FM_STATUS_RESET(&(F2608->OPN.ST), changebits); } /* YM2608(OPNA) */ //void * ym2608_init(void *param, const device_config *device, int clock, int rate, // void *pcmrom,int pcmsize, // FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) void * ym2608_init(void *param, int clock, int rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) { YM2608 *F2608; /* allocate extend state space */ if( (F2608 = (YM2608 *)malloc(sizeof(YM2608)))==NULL) return NULL; /* clear */ memset(F2608,0,sizeof(YM2608)); /* allocate total level table (128kb space) */ if( !init_tables() ) { free( F2608 ); return NULL; } F2608->OPN.ST.param = param; F2608->OPN.type = TYPE_YM2608; F2608->OPN.P_CH = F2608->CH; //F2608->OPN.ST.device = device; F2608->OPN.ST.clock = clock; F2608->OPN.ST.rate = rate; /* External handlers */ F2608->OPN.ST.timer_handler = timer_handler; F2608->OPN.ST.IRQ_Handler = IRQHandler; F2608->OPN.ST.SSG = ssg; /* DELTA-T */ //F2608->deltaT.memory = (UINT8 *)pcmrom; //F2608->deltaT.memory_size = pcmsize; F2608->deltaT.memory = NULL; F2608->deltaT.memory_size = 0x00; F2608->deltaT.memory_mask = 0x00; /*F2608->deltaT.write_time = 20.0 / clock;*/ /* a single byte write takes 20 cycles of main clock */ /*F2608->deltaT.read_time = 18.0 / clock;*/ /* a single byte read takes 18 cycles of main clock */ F2608->deltaT.status_set_handler = YM2608_deltat_status_set; F2608->deltaT.status_reset_handler = YM2608_deltat_status_reset; F2608->deltaT.status_change_which_chip = F2608; F2608->deltaT.status_change_EOS_bit = 0x04; /* status flag: set bit2 on End Of Sample */ F2608->deltaT.status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY */ F2608->deltaT.status_change_ZERO_bit = 0x10; /* status flag: set bit4 if silence continues for more than 290 miliseconds while recording the ADPCM */ /* ADPCM Rhythm */ F2608->pcmbuf = (UINT8*)YM2608_ADPCM_ROM; F2608->pcm_size = 0x2000; Init_ADPCMATable(); #ifdef __STATE_H__ YM2608_save_state(F2608, device); #endif return F2608; } /* shut down emulator */ void ym2608_shutdown(void *chip) { YM2608 *F2608 = (YM2608 *)chip; free(F2608->deltaT.memory); F2608->deltaT.memory = NULL; FMCloseTable(); free(F2608); } /* reset one of chips */ void ym2608_reset_chip(void *chip) { int i; YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; YM_DELTAT *DELTAT = &F2608->deltaT; /* Reset Prescaler */ OPNPrescaler_w(OPN , 0 , 2); F2608->deltaT.freqbase = OPN->ST.freqbase; /* reset SSG section */ (*OPN->ST.SSG->reset)(OPN->ST.param); /* status clear */ FM_BUSY_CLEAR(&OPN->ST); /* register 0x29 - default value after reset is: enable only 3 FM channels and enable all the status flags */ YM2608IRQMaskWrite(OPN, F2608, 0x1f ); /* default value for D4-D0 is 1 */ /* register 0x10, A1=1 - default value is 1 for D4, D3, D2, 0 for the rest */ YM2608IRQFlagWrite(OPN, F2608, 0x1c ); /* default: enable timer A and B, disable EOS, BRDY and ZERO */ OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ OPN->eg_timer = 0; OPN->eg_cnt = 0; FM_STATUS_RESET(&OPN->ST, 0xff); reset_channels( &OPN->ST , F2608->CH , 6 ); /* reset OPerator paramater */ for(i = 0xb6 ; i >= 0xb4 ; i-- ) { OPNWriteReg(OPN,i ,0xc0); OPNWriteReg(OPN,i|0x100,0xc0); } for(i = 0xb2 ; i >= 0x30 ; i-- ) { OPNWriteReg(OPN,i ,0); OPNWriteReg(OPN,i|0x100,0); } for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); /* ADPCM - percussion sounds */ for( i = 0; i < 6; i++ ) { if (i<=3) /* channels 0,1,2,3 */ F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); else /* channels 4 and 5 work with slower clock */ F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/6.0); F2608->adpcm[i].start = YM2608_ADPCM_ROM_addr[i*2]; F2608->adpcm[i].end = YM2608_ADPCM_ROM_addr[i*2+1]; F2608->adpcm[i].now_addr = 0; F2608->adpcm[i].now_step = 0; /* F2608->adpcm[i].delta = 21866; */ F2608->adpcm[i].vol_mul = 0; F2608->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ F2608->adpcm[i].flagMask = 0; F2608->adpcm[i].flag = 0; F2608->adpcm[i].adpcm_acc = 0; F2608->adpcm[i].adpcm_step= 0; F2608->adpcm[i].adpcm_out = 0; } F2608->adpcmTL = 0x3f; F2608->adpcm_arrivedEndAddress = 0; /* not used */ /* DELTA-T unit */ DELTAT->freqbase = OPN->ST.freqbase; DELTAT->output_pointer = OPN->out_delta; DELTAT->portshift = 5; /* always 5bits shift */ /* ASG */ DELTAT->output_range = 1<<23; YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER,YM_DELTAT_EMULATION_MODE_NORMAL); } /* YM2608 write */ /* n = number */ /* a = address */ /* v = value */ int ym2608_write(void *chip, int a,UINT8 v) { YM2608 *F2608 = (YM2608 *)chip; FM_OPN *OPN = &F2608->OPN; int addr; v &= 0xff; /*adjust to 8 bit bus */ switch(a&3) { case 0: /* address port 0 */ OPN->ST.address = v; F2608->addr_A1 = 0; /* Write register to SSG emulator */ if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); /* prescaler selecter : 2d,2e,2f */ if( v >= 0x2d && v <= 0x2f ) { OPNPrescaler_w(OPN , v , 2); //TODO: set ADPCM[c].step F2608->deltaT.freqbase = OPN->ST.freqbase; } break; case 1: /* data port 0 */ if (F2608->addr_A1 != 0) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2608->REGS[addr] = v; switch(addr & 0xf0) { case 0x00: /* SSG section */ /* Write data to SSG emulator */ (*OPN->ST.SSG->write)(OPN->ST.param,a,v); break; case 0x10: /* 0x10-0x1f : Rhythm section */ ym2608_update_req(OPN->ST.param); FM_ADPCMAWrite(F2608,addr-0x10,v); break; case 0x20: /* Mode Register */ switch(addr) { case 0x29: /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ YM2608IRQMaskWrite(OPN, F2608, v); break; default: ym2608_update_req(OPN->ST.param); OPNWriteMode(OPN,addr,v); } break; default: /* OPN section */ ym2608_update_req(OPN->ST.param); OPNWriteReg(OPN,addr,v); } break; case 2: /* address port 1 */ OPN->ST.address = v; F2608->addr_A1 = 1; break; case 3: /* data port 1 */ if (F2608->addr_A1 != 1) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2608->REGS[addr | 0x100] = v; ym2608_update_req(OPN->ST.param); switch( addr & 0xf0 ) { case 0x00: /* DELTAT PORT */ switch( addr ) { case 0x0e: /* DAC data */ #ifdef _DEBUG logerror("YM2608: write to DAC data (unimplemented) value=%02x\n",v); #endif break; default: /* 0x00-0x0d */ YM_DELTAT_ADPCM_Write(&F2608->deltaT,addr,v); } break; case 0x10: /* IRQ Flag control */ if( addr == 0x10 ) { YM2608IRQFlagWrite(OPN, F2608, v); } break; default: OPNWriteReg(OPN,addr | 0x100,v); } } return OPN->ST.irq; } UINT8 ym2608_read(void *chip,int a) { YM2608 *F2608 = (YM2608 *)chip; int addr = F2608->OPN.ST.address; UINT8 ret = 0; switch( a&3 ) { case 0: /* status 0 : YM2203 compatible */ /* BUSY:x:x:x:x:x:FLAGB:FLAGA */ ret = FM_STATUS_FLAG(&F2608->OPN.ST) & 0x83; break; case 1: /* status 0, ID */ if( addr < 16 ) ret = (*F2608->OPN.ST.SSG->read)(F2608->OPN.ST.param); else if(addr == 0xff) ret = 0x01; /* ID code */ break; case 2: /* status 1 : status 0 + ADPCM status */ /* BUSY : x : PCMBUSY : ZERO : BRDY : EOS : FLAGB : FLAGA */ ret = (FM_STATUS_FLAG(&F2608->OPN.ST) & (F2608->flagmask|0x80)) | ((F2608->deltaT.PCM_BSY & 1)<<5) ; break; case 3: if(addr == 0x08) { ret = YM_DELTAT_ADPCM_Read(&F2608->deltaT); } else { if(addr == 0x0f) { #ifdef _DEBUG logerror("YM2608 A/D conversion is accessed but not implemented !\n"); #endif ret = 0x80; /* 2's complement PCM data - result from A/D conversion */ } } break; } return ret; } int ym2608_timer_over(void *chip,int c) { YM2608 *F2608 = (YM2608 *)chip; switch(c) { #if 0 case 2: { /* BUFRDY flag */ YM_DELTAT_BRDY_callback( &F2608->deltaT ); } break; #endif case 1: { /* Timer B */ TimerBOver( &(F2608->OPN.ST) ); } break; case 0: { /* Timer A */ ym2608_update_req(F2608->OPN.ST.param); /* timer update */ TimerAOver( &(F2608->OPN.ST) ); /* CSM mode key,TL controll */ if( F2608->OPN.ST.mode & 0x80 ) { /* CSM mode total level latch and auto key on */ CSMKeyControll( F2608->OPN.type, &(F2608->CH[2]) ); } } break; default: break; } return F2608->OPN.ST.irq; } void ym2608_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, offs_t DataLength, const UINT8* ROMData) { YM2608 *F2608 = (YM2608 *)chip; switch(rom_id) { case 0x01: // ADPCM // unused, it's constant break; case 0x02: // DELTA-T if (F2608->deltaT.memory_size != ROMSize) { F2608->deltaT.memory = (UINT8*)realloc(F2608->deltaT.memory, ROMSize); F2608->deltaT.memory_size = ROMSize; memset(F2608->deltaT.memory, 0xFF, ROMSize); YM_DELTAT_calc_mem_mask(&F2608->deltaT); } if (DataStart > ROMSize) return; if (DataStart + DataLength > ROMSize) DataLength = ROMSize - DataStart; memcpy(F2608->deltaT.memory + DataStart, ROMData, DataLength); break; } return; } void ym2608_set_mutemask(void *chip, UINT32 MuteMask) { YM2608 *F2608 = (YM2608 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 6; CurChn ++) F2608->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; for (CurChn = 0; CurChn < 6; CurChn ++) F2608->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; F2608->MuteDeltaT = (MuteMask >> 12) & 0x01; return; } #endif /* BUILD_YM2608 */ #if (BUILD_YM2610||BUILD_YM2610B) /* YM2610(OPNB) */ /* Generate samples for one of the YM2610s */ void ym2610_update_one(void *chip, FMSAMPLE **buffer, int length) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; int i,j; FMSAMPLE *bufL,*bufR; FM_CH *cch[4]; INT32 *out_fm = OPN->out_fm; /* buffer setup */ bufL = buffer[0]; bufR = buffer[1]; cch[0] = &F2610->CH[1]; cch[1] = &F2610->CH[2]; cch[2] = &F2610->CH[4]; cch[3] = &F2610->CH[5]; #ifdef YM2610B_WARNING #define FM_KEY_IS(SLOT) ((SLOT)->key) #define FM_MSG_YM2610B "YM2610-%p.CH%d is playing,Check whether the type of the chip is YM2610B\n" /* Check YM2610B warning message */ if( FM_KEY_IS(&F2610->CH[0].SLOT[3]) ) { LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,0)); FM_KEY_IS(&F2610->CH[0].SLOT[3]) = 0; } if( FM_KEY_IS(&F2610->CH[3].SLOT[3]) ) { LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,3)); FM_KEY_IS(&F2610->CH[3].SLOT[3]) = 0; } #endif /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[1]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT4] , cch[1]->fc , cch[1]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[1] ); refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); /* buffering */ for(i=0; i < length ; i++) { advance_lfo(OPN); /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[1] = 0; out_fm[2] = 0; out_fm[4] = 0; out_fm[5] = 0; /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); } /* calculate FM */ chan_calc(OPN, cch[0], 1 ); /*remapped to 1*/ chan_calc(OPN, cch[1], 2 ); /*remapped to 2*/ chan_calc(OPN, cch[2], 4 ); /*remapped to 4*/ chan_calc(OPN, cch[3], 5 ); /*remapped to 5*/ /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2610->adpcm[j].flag ) ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); } /* buffering */ { int lt,rt; /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; lt += ((out_fm[1]>>1) & OPN->pan[2]); // the shift right was verified on real chip rt += ((out_fm[1]>>1) & OPN->pan[3]); lt += ((out_fm[2]>>1) & OPN->pan[4]); rt += ((out_fm[2]>>1) & OPN->pan[5]); lt += ((out_fm[4]>>1) & OPN->pan[8]); rt += ((out_fm[4]>>1) & OPN->pan[9]); lt += ((out_fm[5]>>1) & OPN->pan[10]); rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); lt >>= FINAL_SH; rt >>= FINAL_SH; //Limit( lt, MAXOUT, MINOUT ); //Limit( rt, MAXOUT, MINOUT ); #ifdef SAVE_SAMPLE SAVE_ALL_CHANNELS #endif /* buffering */ bufL[i] = lt; bufR[i] = rt; } /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[1] ) } INTERNAL_TIMER_B(&OPN->ST,length) } #if BUILD_YM2610B /* Generate samples for one of the YM2610Bs */ void ym2610b_update_one(void *chip, FMSAMPLE **buffer, int length) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; int i,j; FMSAMPLE *bufL,*bufR; FM_CH *cch[6]; INT32 *out_fm = OPN->out_fm; /* buffer setup */ bufL = buffer[0]; bufR = buffer[1]; cch[0] = &F2610->CH[0]; cch[1] = &F2610->CH[1]; cch[2] = &F2610->CH[2]; cch[3] = &F2610->CH[3]; cch[4] = &F2610->CH[4]; cch[5] = &F2610->CH[5]; /* refresh PG and EG */ refresh_fc_eg_chan( OPN, cch[0] ); refresh_fc_eg_chan( OPN, cch[1] ); if( (OPN->ST.mode & 0xc0) ) { /* 3SLOT MODE */ if( cch[2]->SLOT[SLOT1].Incr==-1) { refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); } } else refresh_fc_eg_chan( OPN, cch[2] ); refresh_fc_eg_chan( OPN, cch[3] ); refresh_fc_eg_chan( OPN, cch[4] ); refresh_fc_eg_chan( OPN, cch[5] ); /* buffering */ for(i=0; i < length ; i++) { advance_lfo(OPN); /* clear output acc. */ OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; /* clear outputs */ out_fm[0] = 0; out_fm[1] = 0; out_fm[2] = 0; out_fm[3] = 0; out_fm[4] = 0; out_fm[5] = 0; /* advance envelope generator */ OPN->eg_timer += OPN->eg_timer_add; while (OPN->eg_timer >= OPN->eg_timer_overflow) { OPN->eg_timer -= OPN->eg_timer_overflow; OPN->eg_cnt++; advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); } /* calculate FM */ chan_calc(OPN, cch[0], 0 ); chan_calc(OPN, cch[1], 1 ); chan_calc(OPN, cch[2], 2 ); chan_calc(OPN, cch[3], 3 ); chan_calc(OPN, cch[4], 4 ); chan_calc(OPN, cch[5], 5 ); /* deltaT ADPCM */ if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) YM_DELTAT_ADPCM_CALC(DELTAT); /* ADPCMA */ for( j = 0; j < 6; j++ ) { if( F2610->adpcm[j].flag ) ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); } /* buffering */ { int lt,rt; /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; lt += ((out_fm[0]>>1) & OPN->pan[0]); // the shift right is verified on YM2610 rt += ((out_fm[0]>>1) & OPN->pan[1]); lt += ((out_fm[1]>>1) & OPN->pan[2]); rt += ((out_fm[1]>>1) & OPN->pan[3]); lt += ((out_fm[2]>>1) & OPN->pan[4]); rt += ((out_fm[2]>>1) & OPN->pan[5]); lt += ((out_fm[3]>>1) & OPN->pan[6]); rt += ((out_fm[3]>>1) & OPN->pan[7]); lt += ((out_fm[4]>>1) & OPN->pan[8]); rt += ((out_fm[4]>>1) & OPN->pan[9]); lt += ((out_fm[5]>>1) & OPN->pan[10]); rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; lt += (out_fm[0] & OPN->pan[0]); rt += (out_fm[0] & OPN->pan[1]); lt += (out_fm[1] & OPN->pan[2]); rt += (out_fm[1] & OPN->pan[3]); lt += (out_fm[2] & OPN->pan[4]); rt += (out_fm[2] & OPN->pan[5]); lt += (out_fm[3] & OPN->pan[6]); rt += (out_fm[3] & OPN->pan[7]); lt += (out_fm[4] & OPN->pan[8]); rt += (out_fm[4] & OPN->pan[9]); lt += (out_fm[5] & OPN->pan[10]); rt += (out_fm[5] & OPN->pan[11]); lt >>= FINAL_SH; rt >>= FINAL_SH; //Limit( lt, MAXOUT, MINOUT ); //Limit( rt, MAXOUT, MINOUT ); #ifdef SAVE_SAMPLE SAVE_ALL_CHANNELS #endif /* buffering */ bufL[i] = lt; bufR[i] = rt; } /* timer A control */ INTERNAL_TIMER_A( &OPN->ST , cch[2] ) } INTERNAL_TIMER_B(&OPN->ST,length) } #endif /* BUILD_YM2610B */ #ifdef __STATE_H__ void ym2610_postload(void *chip) { if (chip) { YM2610 *F2610 = (YM2610 *)chip; int r; /* SSG registers */ for(r=0;r<16;r++) { (*F2610->OPN.ST.SSG->write)(F2610->OPN.ST.param,0,r); (*F2610->OPN.ST.SSG->write)(F2610->OPN.ST.param,1,F2610->REGS[r]); } /* OPN registers */ /* DT / MULTI , TL , KS / AR , AMON / DR , SR , SL / RR , SSG-EG */ for(r=0x30;r<0x9e;r++) if((r&3) != 3) { OPNWriteReg(&F2610->OPN,r,F2610->REGS[r]); OPNWriteReg(&F2610->OPN,r|0x100,F2610->REGS[r|0x100]); } /* FB / CONNECT , L / R / AMS / PMS */ for(r=0xb0;r<0xb6;r++) if((r&3) != 3) { OPNWriteReg(&F2610->OPN,r,F2610->REGS[r]); OPNWriteReg(&F2610->OPN,r|0x100,F2610->REGS[r|0x100]); } /* FM channels */ /*FM_channel_postload(F2610->CH,6);*/ /* rhythm(ADPCMA) */ FM_ADPCMAWrite(F2610,1,F2610->REGS[0x101]); for( r=0 ; r<6 ; r++) { FM_ADPCMAWrite(F2610,r+0x08,F2610->REGS[r+0x108]); FM_ADPCMAWrite(F2610,r+0x10,F2610->REGS[r+0x110]); FM_ADPCMAWrite(F2610,r+0x18,F2610->REGS[r+0x118]); FM_ADPCMAWrite(F2610,r+0x20,F2610->REGS[r+0x120]); FM_ADPCMAWrite(F2610,r+0x28,F2610->REGS[r+0x128]); } /* Delta-T ADPCM unit */ YM_DELTAT_postload(&F2610->deltaT , &F2610->REGS[0x010] ); } } static void YM2610_save_state(YM2610 *F2610, const device_config *device) { state_save_register_device_item_array(device, 0, F2610->REGS); FMsave_state_st(device,&F2610->OPN.ST); FMsave_state_channel(device,F2610->CH,6); /* 3slots */ state_save_register_device_item_array(device, 0, F2610->OPN.SL3.fc); state_save_register_device_item(device, 0, F2610->OPN.SL3.fn_h); state_save_register_device_item_array(device, 0, F2610->OPN.SL3.kcode); /* address register1 */ state_save_register_device_item(device, 0, F2610->addr_A1); state_save_register_device_item(device, 0, F2610->adpcm_arrivedEndAddress); /* rythm(ADPCMA) */ FMsave_state_adpcma(device,F2610->adpcm); /* Delta-T ADPCM unit */ YM_DELTAT_savestate(device,&F2610->deltaT); } #endif /* _STATE_H */ static void YM2610_deltat_status_set(void *chip, UINT8 changebits) { YM2610 *F2610 = (YM2610 *)chip; F2610->adpcm_arrivedEndAddress |= changebits; } static void YM2610_deltat_status_reset(void *chip, UINT8 changebits) { YM2610 *F2610 = (YM2610 *)chip; F2610->adpcm_arrivedEndAddress &= (~changebits); } //void *ym2610_init(void *param, const device_config *device, int clock, int rate, // void *pcmroma,int pcmsizea,void *pcmromb,int pcmsizeb, // FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) void *ym2610_init(void *param, int clock, int rate, FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) { YM2610 *F2610; /* allocate extend state space */ if( (F2610 = (YM2610 *)malloc(sizeof(YM2610)))==NULL) return NULL; /* clear */ memset(F2610,0,sizeof(YM2610)); /* allocate total level table (128kb space) */ if( !init_tables() ) { free( F2610 ); return NULL; } /* FM */ F2610->OPN.ST.param = param; F2610->OPN.type = TYPE_YM2610; F2610->OPN.P_CH = F2610->CH; //F2610->OPN.ST.device = device; F2610->OPN.ST.clock = clock; F2610->OPN.ST.rate = rate; /* Extend handler */ F2610->OPN.ST.timer_handler = timer_handler; F2610->OPN.ST.IRQ_Handler = IRQHandler; F2610->OPN.ST.SSG = ssg; /* ADPCM */ //F2610->pcmbuf = (const UINT8 *)pcmroma; //F2610->pcm_size = pcmsizea; F2610->pcmbuf = NULL; F2610->pcm_size = 0x00; /* DELTA-T */ //F2610->deltaT.memory = (UINT8 *)pcmromb; //F2610->deltaT.memory_size = pcmsizeb; F2610->deltaT.memory = NULL; F2610->deltaT.memory_size = 0x00; F2610->deltaT.memory_mask = 0x00; F2610->deltaT.status_set_handler = YM2610_deltat_status_set; F2610->deltaT.status_reset_handler = YM2610_deltat_status_reset; F2610->deltaT.status_change_which_chip = F2610; F2610->deltaT.status_change_EOS_bit = 0x80; /* status flag: set bit7 on End Of Sample */ Init_ADPCMATable(); #ifdef __STATE_H__ YM2610_save_state(F2610, device); #endif return F2610; } /* shut down emulator */ void ym2610_shutdown(void *chip) { YM2610 *F2610 = (YM2610 *)chip; free(F2610->pcmbuf); F2610->pcmbuf = NULL; free(F2610->deltaT.memory); F2610->deltaT.memory = NULL; FMCloseTable(); free(F2610); } /* reset one of chip */ void ym2610_reset_chip(void *chip) { int i; YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; YM_DELTAT *DELTAT = &F2610->deltaT; /*astring name; device_t* dev = F2610->OPN.ST.device;*/ /* setup PCM buffers again */ /*name.printf("%s",dev->tag()); F2610->pcmbuf = (const UINT8 *)dev->machine->region(name)->base(); F2610->pcm_size = dev->machine->region(name)->bytes(); name.printf("%s.deltat",dev->tag()); F2610->deltaT.memory = (UINT8 *)dev->machine->region(name)->base(); if(F2610->deltaT.memory == NULL) { F2610->deltaT.memory = (UINT8*)F2610->pcmbuf; F2610->deltaT.memory_size = F2610->pcm_size; } else F2610->deltaT.memory_size = dev->machine->region(name)->bytes();*/ /* Reset Prescaler */ OPNSetPres( OPN, 6*24, 6*24, 4*2); /* OPN 1/6 , SSG 1/4 */ /* reset SSG section */ (*OPN->ST.SSG->reset)(OPN->ST.param); /* status clear */ FM_IRQMASK_SET(&OPN->ST,0x03); FM_BUSY_CLEAR(&OPN->ST); OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ OPN->eg_timer = 0; OPN->eg_cnt = 0; FM_STATUS_RESET(&OPN->ST, 0xff); reset_channels( &OPN->ST , F2610->CH , 6 ); /* reset OPerator paramater */ for(i = 0xb6 ; i >= 0xb4 ; i-- ) { OPNWriteReg(OPN,i ,0xc0); OPNWriteReg(OPN,i|0x100,0xc0); } for(i = 0xb2 ; i >= 0x30 ; i-- ) { OPNWriteReg(OPN,i ,0); OPNWriteReg(OPN,i|0x100,0); } for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); /**** ADPCM work initial ****/ for( i = 0; i < 6 ; i++ ) { F2610->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); F2610->adpcm[i].now_addr = 0; F2610->adpcm[i].now_step = 0; F2610->adpcm[i].start = 0; F2610->adpcm[i].end = 0; /* F2610->adpcm[i].delta = 21866; */ F2610->adpcm[i].vol_mul = 0; F2610->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ F2610->adpcm[i].flagMask = 1<adpcm[i].flag = 0; F2610->adpcm[i].adpcm_acc = 0; F2610->adpcm[i].adpcm_step= 0; F2610->adpcm[i].adpcm_out = 0; } F2610->adpcmTL = 0x3f; F2610->adpcm_arrivedEndAddress = 0; /* DELTA-T unit */ DELTAT->freqbase = OPN->ST.freqbase; DELTAT->output_pointer = OPN->out_delta; DELTAT->portshift = 8; /* allways 8bits shift */ DELTAT->output_range = 1<<23; YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER,YM_DELTAT_EMULATION_MODE_YM2610); } /* YM2610 write */ /* n = number */ /* a = address */ /* v = value */ int ym2610_write(void *chip, int a, UINT8 v) { YM2610 *F2610 = (YM2610 *)chip; FM_OPN *OPN = &F2610->OPN; int addr; int ch; v &= 0xff; /* adjust to 8 bit bus */ switch( a&3 ) { case 0: /* address port 0 */ OPN->ST.address = v; F2610->addr_A1 = 0; /* Write register to SSG emulator */ if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); break; case 1: /* data port 0 */ if (F2610->addr_A1 != 0) break; /* verified on real YM2608 */ addr = OPN->ST.address; F2610->REGS[addr] = v; switch(addr & 0xf0) { case 0x00: /* SSG section */ /* Write data to SSG emulator */ (*OPN->ST.SSG->write)(OPN->ST.param,a,v); break; case 0x10: /* DeltaT ADPCM */ ym2610_update_req(OPN->ST.param); switch(addr) { case 0x10: /* control 1 */ case 0x11: /* control 2 */ case 0x12: /* start address L */ case 0x13: /* start address H */ case 0x14: /* stop address L */ case 0x15: /* stop address H */ case 0x19: /* delta-n L */ case 0x1a: /* delta-n H */ case 0x1b: /* volume */ { YM_DELTAT_ADPCM_Write(&F2610->deltaT,addr-0x10,v); } break; case 0x1c: /* FLAG CONTROL : Extend Status Clear/Mask */ { UINT8 statusmask = ~v; /* set arrived flag mask */ for(ch=0;ch<6;ch++) F2610->adpcm[ch].flagMask = statusmask&(1<deltaT.status_change_EOS_bit = statusmask & 0x80; /* status flag: set bit7 on End Of Sample */ /* clear arrived flag */ F2610->adpcm_arrivedEndAddress &= statusmask; } break; default: #ifdef _DEBUG logerror("YM2610: write to unknown deltat register %02x val=%02x\n",addr,v); #endif break; } break; case 0x20: /* Mode Register */ ym2610_update_req(OPN->ST.param); OPNWriteMode(OPN,addr,v); break; default: /* OPN section */ ym2610_update_req(OPN->ST.param); /* write register */ OPNWriteReg(OPN,addr,v); } break; case 2: /* address port 1 */ OPN->ST.address = v; F2610->addr_A1 = 1; break; case 3: /* data port 1 */ if (F2610->addr_A1 != 1) break; /* verified on real YM2608 */ ym2610_update_req(OPN->ST.param); addr = OPN->ST.address; F2610->REGS[addr | 0x100] = v; if( addr < 0x30 ) /* 100-12f : ADPCM A section */ FM_ADPCMAWrite(F2610,addr,v); else OPNWriteReg(OPN,addr | 0x100,v); } return OPN->ST.irq; } UINT8 ym2610_read(void *chip,int a) { YM2610 *F2610 = (YM2610 *)chip; int addr = F2610->OPN.ST.address; UINT8 ret = 0; switch( a&3) { case 0: /* status 0 : YM2203 compatible */ ret = FM_STATUS_FLAG(&F2610->OPN.ST) & 0x83; break; case 1: /* data 0 */ if( addr < 16 ) ret = (*F2610->OPN.ST.SSG->read)(F2610->OPN.ST.param); if( addr == 0xff ) ret = 0x01; break; case 2: /* status 1 : ADPCM status */ /* ADPCM STATUS (arrived End Address) */ /* B,--,A5,A4,A3,A2,A1,A0 */ /* B = ADPCM-B(DELTA-T) arrived end address */ /* A0-A5 = ADPCM-A arrived end address */ ret = F2610->adpcm_arrivedEndAddress; break; case 3: ret = 0; break; } return ret; } int ym2610_timer_over(void *chip,int c) { YM2610 *F2610 = (YM2610 *)chip; if( c ) { /* Timer B */ TimerBOver( &(F2610->OPN.ST) ); } else { /* Timer A */ ym2610_update_req(F2610->OPN.ST.param); /* timer update */ TimerAOver( &(F2610->OPN.ST) ); /* CSM mode key,TL controll */ if( F2610->OPN.ST.mode & 0x80 ) { /* CSM mode total level latch and auto key on */ CSMKeyControll( F2610->OPN.type, &(F2610->CH[2]) ); } } return F2610->OPN.ST.irq; } void ym2610_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, offs_t DataLength, const UINT8* ROMData) { YM2610 *F2610 = (YM2610 *)chip; switch(rom_id) { case 0x01: // ADPCM if (F2610->pcm_size != ROMSize) { F2610->pcmbuf = (UINT8*)realloc(F2610->pcmbuf, ROMSize); F2610->pcm_size = ROMSize; memset(F2610->pcmbuf, 0xFF, ROMSize); } if (DataStart > ROMSize) return; if (DataStart + DataLength > ROMSize) DataLength = ROMSize - DataStart; memcpy(F2610->pcmbuf + DataStart, ROMData, DataLength); break; case 0x02: // DELTA-T if (F2610->deltaT.memory_size != ROMSize) { F2610->deltaT.memory = (UINT8*)realloc(F2610->deltaT.memory, ROMSize); F2610->deltaT.memory_size = ROMSize; memset(F2610->deltaT.memory, 0xFF, ROMSize); YM_DELTAT_calc_mem_mask(&F2610->deltaT); } if (DataStart > ROMSize) return; if (DataStart + DataLength > ROMSize) DataLength = ROMSize - DataStart; memcpy(F2610->deltaT.memory + DataStart, ROMData, DataLength); break; } return; } void ym2610_set_mutemask(void *chip, UINT32 MuteMask) { YM2610 *F2610 = (YM2610 *)chip; UINT8 CurChn; for (CurChn = 0; CurChn < 6; CurChn ++) F2610->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; for (CurChn = 0; CurChn < 6; CurChn ++) F2610->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; F2610->MuteDeltaT = (MuteMask >> 12) & 0x01; return; } #endif /* (BUILD_YM2610||BUILD_YM2610B) */ BambooTracker-0.3.5/BambooTracker/chips/mame/fm.h000066400000000000000000000173621362177441300215660ustar00rootroot00000000000000/* File: fm.h -- header file for software emulation for FM sound generator */ #pragma once /* --- select emulation chips --- */ /* #define BUILD_YM2203 (HAS_YM2203) // build YM2203(OPN) emulator #define BUILD_YM2608 (HAS_YM2608) // build YM2608(OPNA) emulator #define BUILD_YM2610 (HAS_YM2610) // build YM2610(OPNB) emulator #define BUILD_YM2610B (HAS_YM2610B) // build YM2610B(OPNB?)emulator #define BUILD_YM2612 (HAS_YM2612) // build YM2612(OPN2) emulator #define BUILD_YM3438 (HAS_YM3438) // build YM3438(OPN) emulator */ //#define BUILD_YM2203 1 #define BUILD_YM2608 1 //#define BUILD_YM2610 1 //#define BUILD_YM2610B 1 //#define BUILD_YM2612 1 //#define BUILD_YM3438 1 /* select bit size of output : 8 or 16 */ #define FM_SAMPLE_BITS 16 /* select timer system internal or external */ #define FM_INTERNAL_TIMER 1 /* --- speedup optimize --- */ /* busy flag enulation , The definition of FM_GET_TIME_NOW() is necessary. */ //#define FM_BUSY_FLAG_SUPPORT 1 /* --- external SSG(YM2149/AY-3-8910)emulator interface port */ /* used by YM2203,YM2608,and YM2610 */ typedef struct _ssg_callbacks ssg_callbacks; struct _ssg_callbacks { void (*set_clock)(void *param, int clock); void (*write)(void *param, int address, int data); int (*read)(void *param); void (*reset)(void *param); }; /* --- external callback funstions for realtime update --- */ #if FM_BUSY_FLAG_SUPPORT #define TIME_TYPE attotime #define UNDEFINED_TIME attotime_zero #define FM_GET_TIME_NOW(machine) timer_get_time(machine) #define ADD_TIMES(t1, t2) attotime_add((t1), (t2)) #define COMPARE_TIMES(t1, t2) attotime_compare((t1), (t2)) #define MULTIPLY_TIME_BY_INT(t,i) attotime_mul(t, i) #endif #if BUILD_YM2203 /* in 2203intf.c */ void ym2203_update_request(void *param); #define ym2203_update_req(chip) ym2203_update_request(chip) #endif /* BUILD_YM2203 */ #if BUILD_YM2608 /* in 2608intf.c */ void ym2608_update_request(void *param); #define ym2608_update_req(chip) ym2608_update_request(chip); #endif /* BUILD_YM2608 */ #if (BUILD_YM2610||BUILD_YM2610B) /* in 2610intf.c */ void ym2610_update_request(void *param); #define ym2610_update_req(chip) ym2610_update_request(chip); #endif /* (BUILD_YM2610||BUILD_YM2610B) */ #if (BUILD_YM2612||BUILD_YM3438) /* in 2612intf.c */ void ym2612_update_request(void *param); #define ym2612_update_req(chip) ym2612_update_request(chip); #endif /* (BUILD_YM2612||BUILD_YM3438) */ /* compiler dependence */ #if 0 #ifndef OSD_CPU_H #define OSD_CPU_H typedef unsigned char UINT8; /* unsigned 8bit */ typedef unsigned short UINT16; /* unsigned 16bit */ typedef unsigned int UINT32; /* unsigned 32bit */ typedef signed char INT8; /* signed 8bit */ typedef signed short INT16; /* signed 16bit */ typedef signed int INT32; /* signed 32bit */ #endif /* OSD_CPU_H */ #endif typedef stream_sample_t FMSAMPLE; /* #if (FM_SAMPLE_BITS==16) typedef INT16 FMSAMPLE; #endif #if (FM_SAMPLE_BITS==8) typedef unsigned char FMSAMPLE; #endif */ typedef void (*FM_TIMERHANDLER)(void *param,int c,int cnt,int clock); typedef void (*FM_IRQHANDLER)(void *param,int irq); /* FM_TIMERHANDLER : Stop or Start timer */ /* int n = chip number */ /* int c = Channel 0=TimerA,1=TimerB */ /* int count = timer count (0=stop) */ /* doube stepTime = step time of one count (sec.)*/ /* FM_IRQHHANDLER : IRQ level changing sense */ /* int n = chip number */ /* int irq = IRQ level 0=OFF,1=ON */ #if BUILD_YM2203 /* -------------------- YM2203(OPN) Interface -------------------- */ /* ** Initialize YM2203 emulator(s). ** ** 'num' is the number of virtual YM2203's to allocate ** 'baseclock' ** 'rate' is sampling rate ** 'TimerHandler' timer callback handler when timer start and clear ** 'IRQHandler' IRQ callback handler when changed IRQ level ** return 0 = success */ //void * ym2203_init(void *param, const device_config *device, int baseclock, int rate, // FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); void * ym2203_init(void *param, int baseclock, int rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); /* ** shutdown the YM2203 emulators */ void ym2203_shutdown(void *chip); /* ** reset all chip registers for YM2203 number 'num' */ void ym2203_reset_chip(void *chip); /* ** update one of chip */ void ym2203_update_one(void *chip, FMSAMPLE **buffer, int length); /* ** Write ** return : InterruptLevel */ int ym2203_write(void *chip,int a,unsigned char v); /* ** Read ** return : InterruptLevel */ unsigned char ym2203_read(void *chip,int a); /* ** Timer OverFlow */ int ym2203_timer_over(void *chip, int c); /* ** State Save */ void ym2203_postload(void *chip); void ym2203_set_mutemask(void *chip, UINT32 MuteMask); #endif /* BUILD_YM2203 */ #if BUILD_YM2608 /* -------------------- YM2608(OPNA) Interface -------------------- */ //void * ym2608_init(void *param, const device_config *device, int baseclock, int rate, // void *pcmroma,int pcmsizea, // FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); void * ym2608_init(void *param, int baseclock, int rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); void ym2608_shutdown(void *chip); void ym2608_reset_chip(void *chip); void ym2608_update_one(void *chip, FMSAMPLE **buffer, int length); int ym2608_write(void *chip, int a, UINT8 v); unsigned char ym2608_read(void *chip,int a); int ym2608_timer_over(void *chip, int c ); void ym2608_postload(void *chip); void ym2608_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, offs_t DataLength, const UINT8* ROMData); void ym2608_set_mutemask(void *chip, UINT32 MuteMask); #endif /* BUILD_YM2608 */ #if (BUILD_YM2610||BUILD_YM2610B) /* -------------------- YM2610(OPNB) Interface -------------------- */ //void * ym2610_init(void *param, const device_config *device, int baseclock, int rate, // void *pcmroma,int pcmasize,void *pcmromb,int pcmbsize, // FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); void * ym2610_init(void *param, int baseclock, int rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); void ym2610_shutdown(void *chip); void ym2610_reset_chip(void *chip); void ym2610_update_one(void *chip, FMSAMPLE **buffer, int length); #if BUILD_YM2610B void ym2610b_update_one(void *chip, FMSAMPLE **buffer, int length); #endif /* BUILD_YM2610B */ int ym2610_write(void *chip, int a,unsigned char v); unsigned char ym2610_read(void *chip,int a); int ym2610_timer_over(void *chip, int c ); void ym2610_postload(void *chip); void ym2610_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, offs_t DataLength, const UINT8* ROMData); void ym2610_set_mutemask(void *chip, UINT32 MuteMask); #endif /* (BUILD_YM2610||BUILD_YM2610B) */ #if (BUILD_YM2612||BUILD_YM3438) //void * ym2612_init(void *param, const device_config *device, int baseclock, int rate, // FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); void * ym2612_init(void *param, int baseclock, int rate, FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); void ym2612_shutdown(void *chip); void ym2612_reset_chip(void *chip); void ym2612_update_one(void *chip, FMSAMPLE **buffer, int length); int ym2612_write(void *chip, int a,unsigned char v); unsigned char ym2612_read(void *chip,int a); int ym2612_timer_over(void *chip, int c ); void ym2612_postload(void *chip); void ym2612_set_mutemask(void *chip, UINT32 MuteMask); void ym2612_setoptions(UINT8 Flags); #endif /* (BUILD_YM2612||BUILD_YM3438) */ BambooTracker-0.3.5/BambooTracker/chips/mame/mamedef.h000066400000000000000000000032651362177441300225570ustar00rootroot00000000000000#ifndef __MAMEDEF_H__ #define __MAMEDEF_H__ #include #include "../chip_def.h" // typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h) /* 8-bit values */ //typedef unsigned char UINT8; //typedef signed char INT8; typedef uint8_t UINT8; typedef int8_t INT8; /* 16-bit values */ //typedef unsigned short UINT16; //typedef signed short INT16; typedef uint16_t UINT16; typedef int16_t INT16; /* 32-bit values */ #ifndef _WINDOWS_H //typedef unsigned int UINT32; //typedef signed int INT32; typedef uint32_t UINT32; typedef int32_t INT32; #endif /* 64-bit values */ #ifndef _WINDOWS_H #ifdef _MSC_VER typedef signed __int64 INT64; typedef unsigned __int64 UINT64; #else __extension__ typedef unsigned long long UINT64; __extension__ typedef signed long long INT64; #endif #endif /* offsets and addresses are 32-bit (for now...) */ typedef UINT32 offs_t; /* stream_sample_t is used to represent a single sample in a sound stream */ //typedef INT32 stream_sample_t; typedef sample stream_sample_t; #if defined(VGM_BIG_ENDIAN) #define BYTE_XOR_BE(x) (x) #elif defined(VGM_LITTLE_ENDIAN) #define BYTE_XOR_BE(x) ((x) ^ 0x01) #else // don't define BYTE_XOR_BE so that it throws an error when compiling #endif #if defined(_MSC_VER) //#define INLINE static __forceinline #define INLINE static __inline #elif defined(__GNUC__) #define INLINE static __inline__ #else #define INLINE static inline #endif #define M_PI 3.14159265358979323846 #ifdef _DEBUG #define logerror printf #else #define logerror #endif extern stream_sample_t* DUMMYBUF[]; typedef void (*SRATE_CALLBACK)(void*, UINT32); #endif // __MAMEDEF_H__ BambooTracker-0.3.5/BambooTracker/chips/mame/ymdeltat.c000066400000000000000000000550601362177441300227770ustar00rootroot00000000000000/* ** ** File: ymdeltat.c ** ** YAMAHA DELTA-T adpcm sound emulation subroutine ** used by fmopl.c (Y8950) and fm.c (YM2608 and YM2610/B) ** ** Base program is YM2610 emulator by Hiromitsu Shioya. ** Written by Tatsuyuki Satoh ** Improvements by Jarek Burczynski (bujar at mame dot net) ** ** ** History: ** ** 03-08-2003 Jarek Burczynski: ** - fixed BRDY flag implementation. ** ** 24-07-2003 Jarek Burczynski, Frits Hilderink: ** - fixed delault value for control2 in YM_DELTAT_ADPCM_Reset ** ** 22-07-2003 Jarek Burczynski, Frits Hilderink: ** - fixed external memory support ** ** 15-06-2003 Jarek Burczynski: ** - implemented CPU -> AUDIO ADPCM synthesis (via writes to the ADPCM data reg $08) ** - implemented support for the Limit address register ** - supported two bits from the control register 2 ($01): RAM TYPE (x1 bit/x8 bit), ROM/RAM ** - implemented external memory access (read/write) via the ADPCM data reg reads/writes ** Thanks go to Frits Hilderink for the example code. ** ** 14-06-2003 Jarek Burczynski: ** - various fixes to enable proper support for status register flags: BSRDY, PCM BSY, ZERO ** - modified EOS handling ** ** 05-04-2003 Jarek Burczynski: ** - implemented partial support for external/processor memory on sample replay ** ** 01-12-2002 Jarek Burczynski: ** - fixed first missing sound in gigandes thanks to previous fix (interpolator) by ElSemi ** - renamed/removed some YM_DELTAT struct fields ** ** 28-12-2001 Acho A. Tang ** - added EOS status report on ADPCM playback. ** ** 05-08-2001 Jarek Burczynski: ** - now_step is initialized with 0 at the start of play. ** ** 12-06-2001 Jarek Burczynski: ** - corrected end of sample bug in YM_DELTAT_ADPCM_CALC. ** Checked on real YM2610 chip - address register is 24 bits wide. ** Thanks go to Stefan Jokisch (stefan.jokisch@gmx.de) for tracking down the problem. ** ** TO DO: ** Check size of the address register on the other chips.... ** ** Version 0.72 ** ** sound chips that have this unit: ** YM2608 OPNA ** YM2610/B OPNB ** Y8950 MSX AUDIO ** */ #include "mamedef.h" #include //#include "sndintrf.h" #include "ymdeltat.h" #define YM_DELTAT_DELTA_MAX (24576) #define YM_DELTAT_DELTA_MIN (127) #define YM_DELTAT_DELTA_DEF (127) #define YM_DELTAT_DECODE_RANGE 32768 #define YM_DELTAT_DECODE_MIN (-(YM_DELTAT_DECODE_RANGE)) #define YM_DELTAT_DECODE_MAX ((YM_DELTAT_DECODE_RANGE)-1) /* Forecast to next Forecast (rate = *8) */ /* 1/8 , 3/8 , 5/8 , 7/8 , 9/8 , 11/8 , 13/8 , 15/8 */ static const INT32 ym_deltat_decode_tableB1[16] = { 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15, }; /* delta to next delta (rate= *64) */ /* 0.9 , 0.9 , 0.9 , 0.9 , 1.2 , 1.6 , 2.0 , 2.4 */ static const INT32 ym_deltat_decode_tableB2[16] = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 }; #if 0 void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT) { logerror("BRDY_callback reached (flag set) !\n"); /* set BRDY bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } #endif UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT) { UINT8 v = 0; /* external memory read */ if ( (DELTAT->portstate & 0xe0)==0x20 ) { /* two dummy reads */ if (DELTAT->memread) { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread--; return 0; } if ( DELTAT->now_addr != (DELTAT->end<<1) ) { v = DELTAT->memory[DELTAT->now_addr>>1]; /*logerror("YM Delta-T memory read $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ DELTAT->now_addr+=2; /* two nibbles at a time */ /* reset BRDY bit in status register, which means we are reading the memory now */ if(DELTAT->status_reset_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); /* setup a timer that will callback us in 10 master clock cycles for Y8950 * in the callback set the BRDY flag to 1 , which means we have another data ready. * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. */ /* set BRDY bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { /* set EOS bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); } } return v; } /* 0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ static const UINT8 dram_rightshift[4]={3,0,0,0}; /* DELTA-T ADPCM write register */ void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v) { if(r>=0x10) return; DELTAT->reg[r] = v; /* stock data */ switch( r ) { case 0x00: /* START: Accessing *external* memory is started when START bit (D7) is set to "1", so you must set all conditions needed for recording/playback before starting. If you access *CPU-managed* memory, recording/playback starts after read/write of ADPCM data register $08. REC: 0 = ADPCM synthesis (playback) 1 = ADPCM analysis (record) MEMDATA: 0 = processor (*CPU-managed*) memory (means: using register $08) 1 = external memory (using start/end/limit registers to access memory: RAM or ROM) SPOFF: controls output pin that should disable the speaker while ADPCM analysis RESET and REPEAT only work with external memory. some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ /* handle emulation mode */ if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { v |= 0x20; /* YM2610 always uses external memory and doesn't even have memory flag bit. */ } DELTAT->portstate = v & (0x80|0x40|0x20|0x10|0x01); /* start, rec, memory mode, repeat flag copy, reset(bit0) */ if( DELTAT->portstate&0x80 )/* START,REC,MEMDATA,REPEAT,SPOFF,--,--,RESET */ { /* set PCM BUSY bit */ DELTAT->PCM_BSY = 1; /* start ADPCM */ DELTAT->now_step = 0; DELTAT->acc = 0; DELTAT->prev_acc = 0; DELTAT->adpcml = 0; DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; DELTAT->now_data = 0; if (DELTAT->start > DELTAT->end) logerror("DeltaT-Warning: Start: %06X, End: %06X\n", DELTAT->start, DELTAT->end); } if( DELTAT->portstate&0x20 ) /* do we access external memory? */ { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread = 2; /* two dummy reads needed before accesing external memory via register $08*/ /* if yes, then let's check if ADPCM memory is mapped and big enough */ if(DELTAT->memory == 0) { #ifdef _DEBUG logerror("YM Delta-T ADPCM rom not mapped\n"); #endif DELTAT->portstate = 0x00; DELTAT->PCM_BSY = 0; } else { if( DELTAT->end >= DELTAT->memory_size ) /* Check End in Range */ { #ifdef _DEBUG logerror("YM Delta-T ADPCM end out of range: $%08x\n", DELTAT->end); #endif DELTAT->end = DELTAT->memory_size - 1; } if( DELTAT->start >= DELTAT->memory_size ) /* Check Start in Range */ { #ifdef _DEBUG logerror("YM Delta-T ADPCM start out of range: $%08x\n", DELTAT->start); #endif DELTAT->portstate = 0x00; DELTAT->PCM_BSY = 0; } } } else /* we access CPU memory (ADPCM data register $08) so we only reset now_addr here */ { DELTAT->now_addr = 0; } if( DELTAT->portstate&0x01 ) { DELTAT->portstate = 0x00; /* clear PCM BUSY bit (in status register) */ DELTAT->PCM_BSY = 0; /* set BRDY flag */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } break; case 0x01: /* L,R,-,-,SAMPLE,DA/AD,RAMTYPE,ROM */ /* handle emulation mode */ if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) { v |= 0x01; /* YM2610 always uses ROM as an external memory and doesn't have ROM/RAM memory flag bit. */ } DELTAT->pan = &DELTAT->output_pointer[(v>>6)&0x03]; if ((DELTAT->control2 & 3) != (v & 3)) { /*0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ if (DELTAT->DRAMportshift != dram_rightshift[v&3]) { DELTAT->DRAMportshift = dram_rightshift[v&3]; /* final shift value depends on chip type and memory type selected: 8 for YM2610 (ROM only), 5 for ROM for Y8950 and YM2608, 5 for x8bit DRAMs for Y8950 and YM2608, 2 for x1bit DRAMs for Y8950 and YM2608. */ /* refresh addresses */ DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); } } DELTAT->control2 = v; break; case 0x02: /* Start Address L */ case 0x03: /* Start Address H */ DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); /*logerror("DELTAT start: 02=%2x 03=%2x addr=%8x\n",DELTAT->reg[0x2], DELTAT->reg[0x3],DELTAT->start );*/ break; case 0x04: /* Stop Address L */ case 0x05: /* Stop Address H */ DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; /*logerror("DELTAT end : 04=%2x 05=%2x addr=%8x\n",DELTAT->reg[0x4], DELTAT->reg[0x5],DELTAT->end );*/ break; case 0x06: /* Prescale L (ADPCM and Record frq) */ case 0x07: /* Prescale H */ break; case 0x08: /* ADPCM data */ /* some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ /* external memory write */ if ( (DELTAT->portstate & 0xe0)==0x60 ) { if (DELTAT->memread) { DELTAT->now_addr = DELTAT->start << 1; DELTAT->memread = 0; } /*logerror("YM Delta-T memory write $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ if ( DELTAT->now_addr != (DELTAT->end<<1) ) { DELTAT->memory[DELTAT->now_addr>>1] = v; DELTAT->now_addr+=2; /* two nibbles at a time */ /* reset BRDY bit in status register, which means we are processing the write */ if(DELTAT->status_reset_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); /* setup a timer that will callback us in 10 master clock cycles for Y8950 * in the callback set the BRDY flag to 1 , which means we have written the data. * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. */ /* set BRDY bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { /* set EOS bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); } return; } /* ADPCM synthesis from CPU */ if ( (DELTAT->portstate & 0xe0)==0x80 ) { DELTAT->CPU_data = v; /* Reset BRDY bit in status register, which means we are full of data */ if(DELTAT->status_reset_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); return; } break; case 0x09: /* DELTA-N L (ADPCM Playback Prescaler) */ case 0x0a: /* DELTA-N H */ DELTAT->delta = (DELTAT->reg[0xa]*0x0100 | DELTAT->reg[0x9]); DELTAT->step = (UINT32)( (double)(DELTAT->delta /* *(1<<(YM_DELTAT_SHIFT-16)) */ ) * (DELTAT->freqbase) ); /*logerror("DELTAT deltan:09=%2x 0a=%2x\n",DELTAT->reg[0x9], DELTAT->reg[0xa]);*/ break; case 0x0b: /* Output level control (volume, linear) */ { INT32 oldvol = DELTAT->volume; DELTAT->volume = (v&0xff) * (DELTAT->output_range/256) / YM_DELTAT_DECODE_RANGE; /* v * ((1<<16)>>8) >> 15; * thus: v * (1<<8) >> 15; * thus: output_range must be (1 << (15+8)) at least * v * ((1<<23)>>8) >> 15; * v * (1<<15) >> 15; */ /*logerror("DELTAT vol = %2x\n",v&0xff);*/ if( oldvol != 0 ) { DELTAT->adpcml = (int)((double)DELTAT->adpcml / (double)oldvol * (double)DELTAT->volume); } } break; case 0x0c: /* Limit Address L */ case 0x0d: /* Limit Address H */ DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); /*logerror("DELTAT limit: 0c=%2x 0d=%2x addr=%8x\n",DELTAT->reg[0xc], DELTAT->reg[0xd],DELTAT->limit );*/ break; } } void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode) { DELTAT->now_addr = 0; DELTAT->now_step = 0; DELTAT->step = 0; DELTAT->start = 0; DELTAT->end = 0; DELTAT->limit = ~0; /* this way YM2610 and Y8950 (both of which don't have limit address reg) will still work */ DELTAT->volume = 0; DELTAT->pan = &DELTAT->output_pointer[pan]; DELTAT->acc = 0; DELTAT->prev_acc = 0; DELTAT->adpcmd = 127; DELTAT->adpcml = 0; DELTAT->emulation_mode = (UINT8)emulation_mode; DELTAT->portstate = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x20 : 0; DELTAT->control2 = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x01 : 0; /* default setting depends on the emulation mode. MSX demo called "facdemo_4" doesn't setup control2 register at all and still works */ DELTAT->DRAMportshift = dram_rightshift[DELTAT->control2 & 3]; /* The flag mask register disables the BRDY after the reset, however ** as soon as the mask is enabled the flag needs to be set. */ /* set BRDY bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } /*void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs) { int r; // to keep adpcml DELTAT->volume = 0; // update for(r=1;r<16;r++) YM_DELTAT_ADPCM_Write(DELTAT,r,regs[r]); DELTAT->reg[0] = regs[0]; // current rom data if (DELTAT->memory) DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1) ); } //void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT) void YM_DELTAT_savestate(YM_DELTAT *DELTAT) { #ifdef __STATE_H__ state_save_register_device_item(device, 0, DELTAT->portstate); state_save_register_device_item(device, 0, DELTAT->now_addr); state_save_register_device_item(device, 0, DELTAT->now_step); state_save_register_device_item(device, 0, DELTAT->acc); state_save_register_device_item(device, 0, DELTAT->prev_acc); state_save_register_device_item(device, 0, DELTAT->adpcmd); state_save_register_device_item(device, 0, DELTAT->adpcml); #endif }*/ #define YM_DELTAT_Limit(val,max,min) \ { \ if ( val > max ) val = max; \ else if ( val < min ) val = min; \ } INLINE void YM_DELTAT_synthesis_from_external_memory(YM_DELTAT *DELTAT) { UINT32 step; int data; DELTAT->now_step += DELTAT->step; if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; DELTAT->now_step &= (1<now_addr == (DELTAT->limit<<1) ) DELTAT->now_addr = 0; if ( DELTAT->now_addr == (DELTAT->end<<1) ) { /* 12-06-2001 JB: corrected comparison. Was > instead of == */ if( DELTAT->portstate&0x10 ){ /* repeat start */ DELTAT->now_addr = DELTAT->start<<1; DELTAT->acc = 0; DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; DELTAT->prev_acc = 0; }else{ /* set EOS bit in status register */ if(DELTAT->status_set_handler) if(DELTAT->status_change_EOS_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); /* clear PCM BUSY bit (reflected in status register) */ DELTAT->PCM_BSY = 0; DELTAT->portstate = 0; DELTAT->adpcml = 0; DELTAT->prev_acc = 0; return; } } if( DELTAT->now_addr&1 ) data = DELTAT->now_data & 0x0f; else { DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1)); data = DELTAT->now_data >> 4; } DELTAT->now_addr++; /* 12-06-2001 JB: */ /* YM2610 address register is 24 bits wide.*/ /* The "+1" is there because we use 1 bit more for nibble calculations.*/ /* WARNING: */ /* Side effect: we should take the size of the mapped ROM into account */ //DELTAT->now_addr &= ( (1<<(24+1))-1); DELTAT->now_addr &= DELTAT->memory_mask; /* store accumulator value */ DELTAT->prev_acc = DELTAT->acc; /* Forecast to next Forecast */ DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); /* delta to next delta */ DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); /* ElSemi: Fix interpolator. */ /*DELTAT->prev_acc = prev_acc + ((DELTAT->acc - prev_acc) / 2 );*/ }while(--step); } /* ElSemi: Fix interpolator. */ DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; /* output for work of output channels (outd[OPNxxxx])*/ *(DELTAT->pan) += DELTAT->adpcml; } INLINE void YM_DELTAT_synthesis_from_CPU_memory(YM_DELTAT *DELTAT) { UINT32 step; int data; DELTAT->now_step += DELTAT->step; if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; DELTAT->now_step &= (1<now_addr&1 ) { data = DELTAT->now_data & 0x0f; DELTAT->now_data = DELTAT->CPU_data; /* after we used CPU_data, we set BRDY bit in status register, * which means we are ready to accept another byte of data */ if(DELTAT->status_set_handler) if(DELTAT->status_change_BRDY_bit) (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); } else { data = DELTAT->now_data >> 4; } DELTAT->now_addr++; /* store accumulator value */ DELTAT->prev_acc = DELTAT->acc; /* Forecast to next Forecast */ DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); /* delta to next delta */ DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); }while(--step); } /* ElSemi: Fix interpolator. */ DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; /* output for work of output channels (outd[OPNxxxx])*/ *(DELTAT->pan) += DELTAT->adpcml; } /* ADPCM B (Delta-T control type) */ void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT) { /* some examples: value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 */ if ( (DELTAT->portstate & 0xe0)==0xa0 ) { YM_DELTAT_synthesis_from_external_memory(DELTAT); return; } if ( (DELTAT->portstate & 0xe0)==0x80 ) { /* ADPCM synthesis from CPU-managed memory (from reg $08) */ YM_DELTAT_synthesis_from_CPU_memory(DELTAT); /* change output based on data in ADPCM data reg ($08) */ return; } //todo: ADPCM analysis // if ( (DELTAT->portstate & 0xe0)==0xc0 ) // if ( (DELTAT->portstate & 0xe0)==0xe0 ) return; } void YM_DELTAT_calc_mem_mask(YM_DELTAT* DELTAT) { UINT32 MaskSize; MaskSize = 0x01; while(MaskSize < DELTAT->memory_size) MaskSize <<= 1; DELTAT->memory_mask = (MaskSize << 1) - 1; // it's Mask<<1 because of the nibbles return; }BambooTracker-0.3.5/BambooTracker/chips/mame/ymdeltat.h000066400000000000000000000064621362177441300230060ustar00rootroot00000000000000#pragma once #define YM_DELTAT_SHIFT (16) #define YM_DELTAT_EMULATION_MODE_NORMAL 0 #define YM_DELTAT_EMULATION_MODE_YM2610 1 typedef void (*STATUS_CHANGE_HANDLER)(void *chip, UINT8 status_bits); /* DELTA-T (adpcm type B) struct */ typedef struct deltat_adpcm_state { /* AT: rearranged and tigntened structure */ UINT8 *memory; INT32 *output_pointer;/* pointer of output pointers */ INT32 *pan; /* pan : &output_pointer[pan] */ double freqbase; #if 0 double write_time; /* Y8950: 10 cycles of main clock; YM2608: 20 cycles of main clock */ double read_time; /* Y8950: 8 cycles of main clock; YM2608: 18 cycles of main clock */ #endif UINT32 memory_size; UINT32 memory_mask; int output_range; UINT32 now_addr; /* current address */ UINT32 now_step; /* currect step */ UINT32 step; /* step */ UINT32 start; /* start address */ UINT32 limit; /* limit address */ UINT32 end; /* end address */ UINT32 delta; /* delta scale */ INT32 volume; /* current volume */ INT32 acc; /* shift Measurement value*/ INT32 adpcmd; /* next Forecast */ INT32 adpcml; /* current value */ INT32 prev_acc; /* leveling value */ UINT8 now_data; /* current rom data */ UINT8 CPU_data; /* current data from reg 08 */ UINT8 portstate; /* port status */ UINT8 control2; /* control reg: SAMPLE, DA/AD, RAM TYPE (x8bit / x1bit), ROM/RAM */ UINT8 portshift; /* address bits shift-left: ** 8 for YM2610, ** 5 for Y8950 and YM2608 */ UINT8 DRAMportshift; /* address bits shift-right: ** 0 for ROM and x8bit DRAMs, ** 3 for x1 DRAMs */ UINT8 memread; /* needed for reading/writing external memory */ /* handlers and parameters for the status flags support */ STATUS_CHANGE_HANDLER status_set_handler; STATUS_CHANGE_HANDLER status_reset_handler; /* note that different chips have these flags on different ** bits of the status register */ void * status_change_which_chip; /* this chip id */ UINT8 status_change_EOS_bit; /* 1 on End Of Sample (record/playback/cycle time of AD/DA converting has passed)*/ UINT8 status_change_BRDY_bit; /* 1 after recording 2 datas (2x4bits) or after reading/writing 1 data */ UINT8 status_change_ZERO_bit; /* 1 if silence lasts for more than 290 miliseconds on ADPCM recording */ /* neither Y8950 nor YM2608 can generate IRQ when PCMBSY bit changes, so instead of above, ** the statusflag gets ORed with PCM_BSY (below) (on each read of statusflag of Y8950 and YM2608) */ UINT8 PCM_BSY; /* 1 when ADPCM is playing; Y8950/YM2608 only */ UINT8 reg[16]; /* adpcm registers */ UINT8 emulation_mode; /* which chip we're emulating */ }YM_DELTAT; /*void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT);*/ UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT); void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v); void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode); void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT); /*void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs); //void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT); void YM_DELTAT_savestate(YM_DELTAT *DELTAT);*/ void YM_DELTAT_calc_mem_mask(YM_DELTAT* DELTAT);BambooTracker-0.3.5/BambooTracker/chips/nuked/000077500000000000000000000000001362177441300211715ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/chips/nuked/nuke2608intf.c000066400000000000000000000141011362177441300234750ustar00rootroot00000000000000#include // for free #include // for memset #include // for NULL #include "nuke2608intf.h" #include "ym3438.h" // Only use EC_EMU2149 //#define ENABLE_ALL_CORES #ifdef ENABLE_ALL_CORES #define EC_MAME 0x01 // AY8910 core from MAME #endif #define EC_EMU2149 0x00 typedef struct _ym2608_state ym2608_state; struct _ym2608_state { ym3438_t * chip; void * psg; int clock; ym2608_interface intf; }; static uint8_t AY_EMU_CORE = 0x00; #define MAX_CHIPS 0x02 static ym2608_state YM2608Data[MAX_CHIPS]; static void psg_set_clock(void *param, int clock) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_set_clock_ym(info->psg, clock); break; #endif case EC_EMU2149: PSG_set_clock((PSG*)info->psg, clock); break; } } } static void psg_write(void *param, int address, int data) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_write_ym(info->psg, address, data); break; #endif case EC_EMU2149: PSG_writeIO((PSG*)info->psg, address, data); break; } } } static int psg_read(void *param) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: return ay8910_read_ym(info->psg); #endif case EC_EMU2149: return PSG_readIO((PSG*)info->psg); } } return 0x00; } static void psg_reset(void *param) { ym2608_state *info = (ym2608_state *)param; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_reset_ym(info->psg); break; #endif case EC_EMU2149: PSG_reset((PSG*)info->psg); break; } } } static const struct OPN2mod_psg_callbacks psgintf = { psg_set_clock, psg_write, psg_read, psg_reset }; void nuke2608_set_ay_emu_core(uint8_t Emulator) { #ifdef ENABLE_ALL_CORES AY_EMU_CORE = (Emulator < 0x02) ? Emulator : 0x00; #else (void)Emulator; AY_EMU_CORE = EC_EMU2149; #endif return; } int device_start_nuke2608(uint8_t ChipID, int clock, uint8_t AYDisable, uint8_t AYFlags, int *AYrate) { static const ym2608_interface generic_2608 = { { AY8910_LEGACY_OUTPUT | AY8910_SINGLE_OUTPUT, AY8910_DEFAULT_LOADS //DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, DEVCB_NULL }, NULL }; ym2608_interface *intf; ym2608_state *info; int rate; int ay_clock; if (ChipID >= MAX_CHIPS) return 0; info = &YM2608Data[ChipID]; info->clock = clock; rate = clock / 144; // FM synthesis rate is clock / 2 / 72 info->intf = generic_2608; intf = &info->intf; if (AYFlags) intf->ay8910_intf.flags = AYFlags; info->chip = (ym3438_t *)malloc(sizeof(ym3438_t)); if (!info->chip) return 0; /* FIXME: Force to use single output */ //info->psg = ay8910_start_ym(NULL, SOUND_YM2608, clock, &intf->ay8910_intf); if (! AYDisable) { ay_clock = clock / 4; *AYrate = ay_clock / 8; switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: info->psg = ay8910_start_ym(NULL, CHTYPE_YM2608, ay_clock, &intf->ay8910_intf); break; #endif case EC_EMU2149: info->psg = PSG_new(ay_clock, *AYrate); if (info->psg == NULL) return 0; PSG_setVolumeMode((PSG*)info->psg, 1); // YM2149 volume mode break; } } else { info->psg = NULL; *AYrate = 0; } OPN2_Reset(info->chip, clock, &psgintf, info); return rate; } void device_stop_nuke2608(uint8_t ChipID) { ym2608_state *info = &YM2608Data[ChipID]; free(info->chip); if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_stop_ym(info->psg); break; #endif case EC_EMU2149: PSG_delete((PSG*)info->psg); break; } info->psg = NULL; } } void device_reset_nuke2608(uint8_t ChipID) { ym2608_state *info = &YM2608Data[ChipID]; OPN2_Reset(info->chip, info->clock, &psgintf, info); } void nuke2608_control_port_a_w(uint8_t ChipID, uint32_t offset, uint8_t data) { (void)offset; ym2608_state *info = &YM2608Data[ChipID]; OPN2_WriteBuffered(info->chip, 0, data); } void nuke2608_control_port_b_w(uint8_t ChipID, uint32_t offset, uint8_t data) { (void)offset; ym2608_state *info = &YM2608Data[ChipID]; OPN2_WriteBuffered(info->chip, 2, data); } void nuke2608_data_port_a_w(uint8_t ChipID, uint32_t offset, uint8_t data) { (void)offset; ym2608_state *info = &YM2608Data[ChipID]; OPN2_WriteBuffered(info->chip, 1, data); } void nuke2608_data_port_b_w(uint8_t ChipID, uint32_t offset, uint8_t data) { (void)offset; ym2608_state *info = &YM2608Data[ChipID]; OPN2_WriteBuffered(info->chip, 3, data); } uint8_t nuke2608_read_port_r(uint8_t ChipID, uint32_t offset) { (void)offset; ym2608_state *info = &YM2608Data[ChipID]; return OPN2_Read(info->chip, 1); } void nuke2608_stream_update(uint8_t ChipID, sample **outputs, int samples) { int i; ym2608_state *info = &YM2608Data[ChipID]; sample *bufl = outputs[0]; sample *bufr = outputs[1]; for (i = 0; i < samples; ++i) { sample lr[2]; OPN2_Generate(info->chip, lr); *bufl++ = lr[0]; *bufr++ = lr[1]; } } void nuke2608_stream_update_ay(uint8_t ChipID, sample **outputs, int samples) { ym2608_state *info = &YM2608Data[ChipID]; if (info->psg != NULL) { switch(AY_EMU_CORE) { #ifdef ENABLE_ALL_CORES case EC_MAME: ay8910_update_one(info->psg, outputs, samples); break; #endif case EC_EMU2149: PSG_calc_stereo((PSG*)info->psg, outputs, samples); break; } } else { memset(outputs[0], 0x00, samples * sizeof(sample)); memset(outputs[1], 0x00, samples * sizeof(sample)); } } struct intf2608 nuked_intf2608 = { .set_ay_emu_core = &nuke2608_set_ay_emu_core, .device_start = &device_start_nuke2608, .device_stop = &device_stop_nuke2608, .device_reset = &device_reset_nuke2608, .control_port_a_w = &nuke2608_control_port_a_w, .control_port_b_w = &nuke2608_control_port_b_w, .data_port_a_w = &nuke2608_data_port_a_w, .data_port_b_w = &nuke2608_data_port_b_w, .read_port_r = &nuke2608_read_port_r, .stream_update = &nuke2608_stream_update, .stream_update_ay = &nuke2608_stream_update_ay, }; BambooTracker-0.3.5/BambooTracker/chips/nuked/nuke2608intf.h000066400000000000000000000002431362177441300235040ustar00rootroot00000000000000#pragma once #include "ym3438.h" #include "../mame/emu2149.h" #ifdef INCLUDE_AY8910_H #include "../mame/ay8910.h" #endif extern struct intf2608 nuked_intf2608; BambooTracker-0.3.5/BambooTracker/chips/nuked/ym3438.c000066400000000000000000001533731362177441300223200ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) * Copyright (C) 2019 Jean Pierre Cimalando * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * * Nuked OPN2-MOD emulator, with OPNA functionality added. * Thanks: * Silicon Pr0n: * Yamaha YM3438 decap and die shot(digshadow). * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): * OPL2 ROMs. * * version: 1.0.9 * * OPN-MOD additions: * - Jean Pierre Cimalando 2019-04-06: add SSG control interface * - Jean Pierre Cimalando 2019-04-06: add 6-channel FM flag * - Jean Pierre Cimalando 2019-04-06: add ADPCM rhythm channels * - Jean Pierre Cimalando 2019-04-07: raise the channel clipping threshold */ #include #include "ym3438.h" /*OPN-MOD: define the quantization in bits at which channels clip*/ static const unsigned channel_clipbits = 11; /*YM2612 has 9 bits*/ enum { eg_num_attack = 0, eg_num_decay = 1, eg_num_sustain = 2, eg_num_release = 3 }; /* logsin table */ static const Bit16u logsinrom[256] = { 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 }; /* exp table */ static const Bit16u exprom[256] = { 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa }; /* Note table */ static const Bit32u fn_note[16] = { 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 }; /* Envelope generator */ static const Bit32u eg_stephi[4][4] = { { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 0, 1, 0 }, { 1, 1, 1, 0 } }; static const Bit8u eg_am_shift[4] = { 7, 3, 1, 0 }; /* Phase generator */ static const Bit32u pg_detune[8] = { 16, 17, 19, 20, 22, 24, 27, 29 }; static const Bit32u pg_lfo_sh1[8][8] = { { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 7, 1, 1 }, { 7, 7, 7, 7, 1, 1, 1, 1 }, { 7, 7, 7, 1, 1, 1, 1, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 }, { 7, 7, 1, 1, 0, 0, 0, 0 } }; static const Bit32u pg_lfo_sh2[8][8] = { { 7, 7, 7, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 2, 2, 2, 2 }, { 7, 7, 7, 2, 2, 2, 7, 7 }, { 7, 7, 2, 2, 7, 7, 2, 2 }, { 7, 7, 2, 7, 7, 7, 2, 7 }, { 7, 7, 7, 2, 7, 7, 2, 1 }, { 7, 7, 7, 2, 7, 7, 2, 1 }, { 7, 7, 7, 2, 7, 7, 2, 1 } }; /* Address decoder */ static const Bit32u op_offset[12] = { 0x000, /* Ch1 OP1/OP2 */ 0x001, /* Ch2 OP1/OP2 */ 0x002, /* Ch3 OP1/OP2 */ 0x100, /* Ch4 OP1/OP2 */ 0x101, /* Ch5 OP1/OP2 */ 0x102, /* Ch6 OP1/OP2 */ 0x004, /* Ch1 OP3/OP4 */ 0x005, /* Ch2 OP3/OP4 */ 0x006, /* Ch3 OP3/OP4 */ 0x104, /* Ch4 OP3/OP4 */ 0x105, /* Ch5 OP3/OP4 */ 0x106 /* Ch6 OP3/OP4 */ }; static const Bit32u ch_offset[6] = { 0x000, /* Ch1 */ 0x001, /* Ch2 */ 0x002, /* Ch3 */ 0x100, /* Ch4 */ 0x101, /* Ch5 */ 0x102 /* Ch6 */ }; /* LFO */ static const Bit32u lfo_cycles[8] = { 108, 77, 71, 67, 62, 44, 8, 5 }; /* FM algorithm */ static const Bit32u fm_algorithm[4][6][8] = { { { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_0 */ { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_1 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 1 } /* Out */ }, { { 0, 1, 0, 0, 0, 1, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 1, 1, 1, 0, 0, 0, 0, 0 }, /* OP2 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 1, 1, 1 } /* Out */ }, { { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ { 1, 0, 0, 1, 1, 1, 1, 0 }, /* Last operator */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ { 0, 0, 0, 0, 1, 1, 1, 1 } /* Out */ }, { { 0, 0, 1, 0, 0, 1, 0, 0 }, /* OP1_0 */ { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ { 0, 0, 0, 1, 0, 0, 0, 0 }, /* OP2 */ { 1, 1, 0, 1, 1, 0, 0, 0 }, /* Last operator */ { 0, 0, 1, 0, 0, 0, 0, 0 }, /* Last operator */ { 1, 1, 1, 1, 1, 1, 1, 1 } /* Out */ } }; /*OPN-MOD: ADPCM jedi table*/ static const Bit16s adpcm_jedi_table[49 * 16] = { 2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30, 2, 6, 10, 14, 19, 23, 27, 31, -2, -6, -10, -14, -19, -23, -27, -31, 2, 7, 11, 16, 21, 26, 30, 35, -2, -7, -11, -16, -21, -26, -30, -35, 2, 7, 13, 18, 23, 28, 34, 39, -2, -7, -13, -18, -23, -28, -34, -39, 2, 8, 14, 20, 25, 31, 37, 43, -2, -8, -14, -20, -25, -31, -37, -43, 3, 9, 15, 21, 28, 34, 40, 46, -3, -9, -15, -21, -28, -34, -40, -46, 3, 10, 17, 24, 31, 38, 45, 52, -3, -10, -17, -24, -31, -38, -45, -52, 3, 11, 19, 27, 34, 42, 50, 58, -3, -11, -19, -27, -34, -42, -50, -58, 4, 12, 21, 29, 38, 46, 55, 63, -4, -12, -21, -29, -38, -46, -55, -63, 4, 13, 23, 32, 41, 50, 60, 69, -4, -13, -23, -32, -41, -50, -60, -69, 5, 15, 25, 35, 46, 56, 66, 76, -5, -15, -25, -35, -46, -56, -66, -76, 5, 16, 28, 39, 50, 61, 73, 84, -5, -16, -28, -39, -50, -61, -73, -84, 6, 18, 31, 43, 56, 68, 81, 93, -6, -18, -31, -43, -56, -68, -81, -93, 6, 20, 34, 48, 61, 75, 89, 103, -6, -20, -34, -48, -61, -75, -89, -103, 7, 22, 37, 52, 67, 82, 97, 112, -7, -22, -37, -52, -67, -82, -97, -112, 8, 24, 41, 57, 74, 90, 107, 123, -8, -24, -41, -57, -74, -90, -107, -123, 9, 27, 45, 63, 82, 100, 118, 136, -9, -27, -45, -63, -82, -100, -118, -136, 10, 30, 50, 70, 90, 110, 130, 150, -10, -30, -50, -70, -90, -110, -130, -150, 11, 33, 55, 77, 99, 121, 143, 165, -11, -33, -55, -77, -99, -121, -143, -165, 12, 36, 60, 84, 109, 133, 157, 181, -12, -36, -60, -84, -109, -133, -157, -181, 13, 40, 66, 93, 120, 147, 173, 200, -13, -40, -66, -93, -120, -147, -173, -200, 14, 44, 73, 103, 132, 162, 191, 221, -14, -44, -73, -103, -132, -162, -191, -221, 16, 48, 81, 113, 146, 178, 211, 243, -16, -48, -81, -113, -146, -178, -211, -243, 17, 53, 89, 125, 160, 196, 232, 268, -17, -53, -89, -125, -160, -196, -232, -268, 19, 58, 98, 137, 176, 215, 255, 294, -19, -58, -98, -137, -176, -215, -255, -294, 21, 64, 108, 151, 194, 237, 281, 324, -21, -64, -108, -151, -194, -237, -281, -324, 23, 71, 118, 166, 213, 261, 308, 356, -23, -71, -118, -166, -213, -261, -308, -356, 26, 78, 130, 182, 235, 287, 339, 391, -26, -78, -130, -182, -235, -287, -339, -391, 28, 86, 143, 201, 258, 316, 373, 431, -28, -86, -143, -201, -258, -316, -373, -431, 31, 94, 158, 221, 284, 347, 411, 474, -31, -94, -158, -221, -284, -347, -411, -474, 34, 104, 174, 244, 313, 383, 453, 523, -34, -104, -174, -244, -313, -383, -453, -523, 38, 115, 191, 268, 345, 422, 498, 575, -38, -115, -191, -268, -345, -422, -498, -575, 42, 126, 210, 294, 379, 463, 547, 631, -42, -126, -210, -294, -379, -463, -547, -631, 46, 139, 231, 324, 417, 510, 602, 695, -46, -139, -231, -324, -417, -510, -602, -695, 51, 153, 255, 357, 459, 561, 663, 765, -51, -153, -255, -357, -459, -561, -663, -765, 56, 168, 280, 392, 505, 617, 729, 841, -56, -168, -280, -392, -505, -617, -729, -841, 61, 185, 308, 432, 555, 679, 802, 926, -61, -185, -308, -432, -555, -679, -802, -926, 68, 204, 340, 476, 612, 748, 884, 1020, -68, -204, -340, -476, -612, -748, -884, -1020, 74, 224, 373, 523, 672, 822, 971, 1121, -74, -224, -373, -523, -672, -822, -971, -1121, 82, 246, 411, 575, 740, 904, 1069, 1233, -82, -246, -411, -575, -740, -904, -1069, -1233, 90, 271, 452, 633, 814, 995, 1176, 1357, -90, -271, -452, -633, -814, -995, -1176, -1357, 99, 298, 497, 696, 895, 1094, 1293, 1492, -99, -298, -497, -696, -895, -1094, -1293, -1492, 109, 328, 547, 766, 985, 1204, 1423, 1642, -109, -328, -547, -766, -985, -1204, -1423, -1642, 120, 361, 601, 842, 1083, 1324, 1564, 1805, -120, -361, -601, -842, -1083, -1324, -1564, -1805, 132, 397, 662, 927, 1192, 1457, 1722, 1987, -132, -397, -662, -927, -1192, -1457, -1722, -1987, 145, 437, 728, 1020, 1311, 1603, 1894, 2186, -145, -437, -728, -1020, -1311, -1603, -1894, -2186, 160, 480, 801, 1121, 1442, 1762, 2083, 2403, -160, -480, -801, -1121, -1442, -1762, -2083, -2403, 176, 529, 881, 1234, 1587, 1940, 2292, 2645, -176, -529, -881, -1234, -1587, -1940, -2292, -2645, 194, 582, 970, 1358, 1746, 2134, 2522, 2910, -194, -582, -970, -1358, -1746, -2134, -2522, -2910 }; /*OPN-MOD: ADPCM table*/ extern const unsigned int YM2608_ADPCM_ROM_addr[2*6]; extern const unsigned char YM2608_ADPCM_ROM[0x2000]; /*OPN-MOD: ADPCM constants*/ static const Bitu adpcm_shift = 16; static const int adpcm_step_inc[8] = { -1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16 }; static Bit32u chip_type = ym3438_mode_readmode; /*OPNMOD: update ADPCM volume*/ void OPNmod_RhythmUpdateVolume(ym3438_t *chip, Bit32u channel) { Bit8u volume = chip->rhythm_tl + chip->rhythm_level[channel]; /* volume formula from MAME OPN */ if (volume >= 63) /* This is correct, 63 = quiet */ { chip->rhythm_vol_mul[channel] = 0; chip->rhythm_vol_shift[channel] = 0; } else { chip->rhythm_vol_mul[channel] = 15 - (volume & 7); /* so called 0.75 dB */ chip->rhythm_vol_shift[channel] = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ } } void OPN2_DoIO(ym3438_t *chip) { /* Write signal check */ chip->write_a_en = (chip->write_a & 0x03) == 0x01; chip->write_d_en = (chip->write_d & 0x03) == 0x01; chip->write_a <<= 1; chip->write_d <<= 1; /* Busy counter */ chip->busy = chip->write_busy; chip->write_busy_cnt += chip->write_busy; chip->write_busy = (chip->write_busy && !(chip->write_busy_cnt >> 5)) || chip->write_d_en; chip->write_busy_cnt &= 0x1f; } void OPN2_DoRegWrite(ym3438_t *chip) { Bit32u i; Bit32u slot = chip->cycles % 12; Bit32u address; Bit32u channel = chip->channel; /* Update registers */ if (chip->write_fm_data) { /* Slot */ if (op_offset[slot] == (chip->address & 0x107)) { if (chip->address & 0x08) { /* OP2, OP4 */ slot += 12; } address = chip->address & 0xf0; switch (address) { case 0x30: /* DT, MULTI */ chip->multi[slot] = chip->data & 0x0f; if (!chip->multi[slot]) { chip->multi[slot] = 1; } else { chip->multi[slot] <<= 1; } chip->dt[slot] = (chip->data >> 4) & 0x07; break; case 0x40: /* TL */ chip->tl[slot] = chip->data & 0x7f; break; case 0x50: /* KS, AR */ chip->ar[slot] = chip->data & 0x1f; chip->ks[slot] = (chip->data >> 6) & 0x03; break; case 0x60: /* AM, DR */ chip->dr[slot] = chip->data & 0x1f; chip->am[slot] = (chip->data >> 7) & 0x01; break; case 0x70: /* SR */ chip->sr[slot] = chip->data & 0x1f; break; case 0x80: /* SL, RR */ chip->rr[slot] = chip->data & 0x0f; chip->sl[slot] = (chip->data >> 4) & 0x0f; chip->sl[slot] |= (chip->sl[slot] + 1) & 0x10; break; case 0x90: /* SSG-EG */ chip->ssg_eg[slot] = chip->data & 0x0f; break; default: break; } } /* Channel */ if (ch_offset[channel] == (chip->address & 0x103)) { address = chip->address & 0xfc; switch (address) { case 0xa0: chip->fnum[channel] = (chip->data & 0xff) | ((chip->reg_a4 & 0x07) << 8); chip->block[channel] = (chip->reg_a4 >> 3) & 0x07; chip->kcode[channel] = (chip->block[channel] << 2) | fn_note[chip->fnum[channel] >> 7]; break; case 0xa4: chip->reg_a4 = chip->data & 0xff; break; case 0xa8: chip->fnum_3ch[channel] = (chip->data & 0xff) | ((chip->reg_ac & 0x07) << 8); chip->block_3ch[channel] = (chip->reg_ac >> 3) & 0x07; chip->kcode_3ch[channel] = (chip->block_3ch[channel] << 2) | fn_note[chip->fnum_3ch[channel] >> 7]; break; case 0xac: chip->reg_ac = chip->data & 0xff; break; case 0xb0: chip->connect[channel] = chip->data & 0x07; chip->fb[channel] = (chip->data >> 3) & 0x07; break; case 0xb4: chip->pms[channel] = chip->data & 0x07; chip->ams[channel] = (chip->data >> 4) & 0x03; chip->pan_l[channel] = (chip->data >> 7) & 0x01; chip->pan_r[channel] = (chip->data >> 6) & 0x01; break; default: break; } } } if (chip->write_a_en || chip->write_d_en) { /* Data */ if (chip->write_a_en) { chip->write_fm_data = 0; } if (chip->write_d_en) { chip->write_fm_data = 1; } /* Address */ if (chip->write_a_en) { /*OPN-MOD: store address for both FM/SSG modes (for PSG read) */ chip->address = chip->write_data; if ((chip->write_data & 0xf0) != 0x00) { /* FM Write */ chip->write_fm_address = 1; } else { /* SSG write */ chip->write_fm_address = 0; } } /* FM Mode */ /* Data */ if (chip->write_d_en && (chip->write_data & 0x100) == 0) { switch (chip->write_fm_mode_a) { /*OPN-MOD: Rhythm key on/off*/ case 0x10: if ((chip->write_data & 0x80) == 0) { for (i = 0; i < 6; ++i) { if (chip->write_data & (1 << i)) { chip->rhythm_key[i] = 1; chip->rhythm_addr[i] = YM2608_ADPCM_ROM_addr[2 * i] << 1; chip->rhythm_now_step[i] = 0; chip->rhythm_adpcm_step[i] = 0; chip->rhythm_adpcm_acc[i] = 0; } } } else { for (i = 0; i < 6; ++i) { if (chip->write_data & (1 << i)) { chip->rhythm_key[i] = 0; chip->rhythml[i] = 0; chip->rhythmr[i] = 0; } } } break; /*OPN-MOD: Rhythm total level*/ case 0x11: chip->rhythm_tl = ~chip->write_data & 63; for (i = 0; i < 6; ++i) { OPNmod_RhythmUpdateVolume(chip, i); } break; /*OPN-MOD: Rhythm instrument pan/level*/ case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: i = chip->write_fm_mode_a - 0x18; chip->rhythm_pan[i] = (chip->write_data >> 6) & 3; chip->rhythm_level[i] = ~chip->write_data & 31; OPNmod_RhythmUpdateVolume(chip, i); break; case 0x21: /* LSI test 1 */ for (i = 0; i < 8; i++) { chip->mode_test_21[i] = (chip->write_data >> i) & 0x01; } break; case 0x22: /* LFO control */ if ((chip->write_data >> 3) & 0x01) { chip->lfo_en = 0x7f; } else { chip->lfo_en = 0; } chip->lfo_freq = chip->write_data & 0x07; break; case 0x24: /* Timer A */ chip->timer_a_reg &= 0x03; chip->timer_a_reg |= (chip->write_data & 0xff) << 2; break; case 0x25: chip->timer_a_reg &= 0x3fc; chip->timer_a_reg |= chip->write_data & 0x03; break; case 0x26: /* Timer B */ chip->timer_b_reg = chip->write_data & 0xff; break; case 0x27: /* CSM, Timer control */ chip->mode_ch3 = (chip->write_data & 0xc0) >> 6; chip->mode_csm = chip->mode_ch3 == 2; chip->timer_a_load = chip->write_data & 0x01; chip->timer_a_enable = (chip->write_data >> 2) & 0x01; chip->timer_a_reset = (chip->write_data >> 4) & 0x01; chip->timer_b_load = (chip->write_data >> 1) & 0x01; chip->timer_b_enable = (chip->write_data >> 3) & 0x01; chip->timer_b_reset = (chip->write_data >> 5) & 0x01; break; case 0x28: /* Key on/off */ for (i = 0; i < 4; i++) { chip->mode_kon_operator[i] = (chip->write_data >> (4 + i)) & 0x01; } if ((chip->write_data & 0x03) == 0x03) { /* Invalid address */ chip->mode_kon_channel = 0xff; } else { /*OPN-MOD: select according to 6 FM channel flag*/ chip->mode_kon_channel = chip->write_data & 0x03; if (chip->mode_fm6ch && (chip->write_data & 0x04)) { chip->mode_kon_channel += 3; } } break; case 0x29: /*OPN-MOD: set 6 FM channel flag*/ chip->mode_fm6ch = chip->write_data & 0x80; break; case 0x2a: /* DAC data */ chip->dacdata &= 0x01; chip->dacdata |= (chip->write_data ^ 0x80) << 1; break; case 0x2b: /* DAC enable */ chip->dacen = chip->write_data >> 7; break; case 0x2c: /* LSI test 2 */ for (i = 0; i < 8; i++) { chip->mode_test_2c[i] = (chip->write_data >> i) & 0x01; } chip->dacdata &= 0x1fe; chip->dacdata |= chip->mode_test_2c[3]; chip->eg_custom_timer = !chip->mode_test_2c[7] && chip->mode_test_2c[6]; break; default: /*OPN-MOD: write $00-$0F to PSG*/ if (chip->write_fm_mode_a < 0x10) { chip->psg->Write(chip->psgdata, 0, chip->write_fm_mode_a); chip->psg->Write(chip->psgdata, 1, chip->write_data); } break; } } /* Address */ if (chip->write_a_en) { chip->write_fm_mode_a = chip->write_data & 0x1ff; } } if (chip->write_fm_data) { chip->data = chip->write_data & 0xff; } } void OPN2_PhaseCalcIncrement(ym3438_t *chip) { Bit32u chan = chip->channel; Bit32u slot = chip->cycles; Bit32u fnum = chip->pg_fnum; Bit32u fnum_h = fnum >> 4; Bit32u fm; Bit32u basefreq; Bit8u lfo = chip->lfo_pm; Bit8u lfo_l = lfo & 0x0f; Bit8u pms = chip->pms[chan]; Bit8u dt = chip->dt[slot]; Bit8u dt_l = dt & 0x03; Bit8u detune = 0; Bit8u block, note; Bit8u sum, sum_h, sum_l; Bit8u kcode = chip->pg_kcode; fnum <<= 1; /* Apply LFO */ if (lfo_l & 0x08) { lfo_l ^= 0x0f; } fm = (fnum_h >> pg_lfo_sh1[pms][lfo_l]) + (fnum_h >> pg_lfo_sh2[pms][lfo_l]); if (pms > 5) { fm <<= pms - 5; } fm >>= 2; if (lfo & 0x10) { fnum -= fm; } else { fnum += fm; } fnum &= 0xfff; basefreq = (fnum << chip->pg_block) >> 2; /* Apply detune */ if (dt_l) { if (kcode > 0x1c) { kcode = 0x1c; } block = kcode >> 2; note = kcode & 0x03; sum = block + 9 + ((dt_l == 3) | (dt_l & 0x02)); sum_h = sum >> 1; sum_l = sum & 0x01; detune = pg_detune[(sum_l << 2) | note] >> (9 - sum_h); } if (dt & 0x04) { basefreq -= detune; } else { basefreq += detune; } basefreq &= 0x1ffff; chip->pg_inc[slot] = (basefreq * chip->multi[slot]) >> 1; chip->pg_inc[slot] &= 0xfffff; } void OPN2_PhaseGenerate(ym3438_t *chip) { Bit32u slot; /* Mask increment */ slot = (chip->cycles + 20) % 24; if (chip->pg_reset[slot]) { chip->pg_inc[slot] = 0; } /* Phase step */ slot = (chip->cycles + 19) % 24; chip->pg_phase[slot] += chip->pg_inc[slot]; chip->pg_phase[slot] &= 0xfffff; if (chip->pg_reset[slot] || chip->mode_test_21[3]) { chip->pg_phase[slot] = 0; } } void OPN2_EnvelopeSSGEG(ym3438_t *chip) { Bit32u slot = chip->cycles; Bit8u direction = 0; chip->eg_ssg_pgrst_latch[slot] = 0; chip->eg_ssg_repeat_latch[slot] = 0; chip->eg_ssg_hold_up_latch[slot] = 0; chip->eg_ssg_inv[slot] = 0; if (chip->ssg_eg[slot] & 0x08) { direction = chip->eg_ssg_dir[slot]; if (chip->eg_level[slot] & 0x200) { /* Reset */ if ((chip->ssg_eg[slot] & 0x03) == 0x00) { chip->eg_ssg_pgrst_latch[slot] = 1; } /* Repeat */ if ((chip->ssg_eg[slot] & 0x01) == 0x00) { chip->eg_ssg_repeat_latch[slot] = 1; } /* Inverse */ if ((chip->ssg_eg[slot] & 0x03) == 0x02) { direction ^= 1; } if ((chip->ssg_eg[slot] & 0x03) == 0x03) { direction = 1; } } /* Hold up */ if (chip->eg_kon_latch[slot] && ((chip->ssg_eg[slot] & 0x07) == 0x05 || (chip->ssg_eg[slot] & 0x07) == 0x03)) { chip->eg_ssg_hold_up_latch[slot] = 1; } direction &= chip->eg_kon[slot]; chip->eg_ssg_inv[slot] = (chip->eg_ssg_dir[slot] ^ ((chip->ssg_eg[slot] >> 2) & 0x01)) & chip->eg_kon[slot]; } chip->eg_ssg_dir[slot] = direction; chip->eg_ssg_enable[slot] = (chip->ssg_eg[slot] >> 3) & 0x01; } void OPN2_EnvelopeADSR(ym3438_t *chip) { Bit32u slot = (chip->cycles + 22) % 24; Bit8u nkon = chip->eg_kon_latch[slot]; Bit8u okon = chip->eg_kon[slot]; Bit8u kon_event; Bit8u koff_event; Bit8u eg_off; Bit16s level; Bit16s nextlevel = 0; Bit16s ssg_level; Bit8u nextstate = chip->eg_state[slot]; Bit16s inc = 0; chip->eg_read[0] = chip->eg_read_inc; chip->eg_read_inc = chip->eg_inc > 0; /* Reset phase generator */ chip->pg_reset[slot] = (nkon && !okon) || chip->eg_ssg_pgrst_latch[slot]; /* KeyOn/Off */ kon_event = (nkon && !okon) || (okon && chip->eg_ssg_repeat_latch[slot]); koff_event = okon && !nkon; ssg_level = level = (Bit16s)chip->eg_level[slot]; if (chip->eg_ssg_inv[slot]) { /* Inverse */ ssg_level = 512 - level; ssg_level &= 0x3ff; } if (koff_event) { level = ssg_level; } if (chip->eg_ssg_enable[slot]) { eg_off = level >> 9; } else { eg_off = (level & 0x3f0) == 0x3f0; } nextlevel = level; if (kon_event) { nextstate = eg_num_attack; /* Instant attack */ if (chip->eg_ratemax) { nextlevel = 0; } else if (chip->eg_state[slot] == eg_num_attack && level != 0 && chip->eg_inc && nkon) { inc = (~level << chip->eg_inc) >> 5; } } else { switch (chip->eg_state[slot]) { case eg_num_attack: if (level == 0) { nextstate = eg_num_decay; } else if(chip->eg_inc && !chip->eg_ratemax && nkon) { inc = (~level << chip->eg_inc) >> 5; } break; case eg_num_decay: if ((level >> 5) == chip->eg_sl[1]) { nextstate = eg_num_sustain; } else if (!eg_off && chip->eg_inc) { inc = 1 << (chip->eg_inc - 1); if (chip->eg_ssg_enable[slot]) { inc <<= 2; } } break; case eg_num_sustain: case eg_num_release: if (!eg_off && chip->eg_inc) { inc = 1 << (chip->eg_inc - 1); if (chip->eg_ssg_enable[slot]) { inc <<= 2; } } break; default: break; } if (!nkon) { nextstate = eg_num_release; } } if (chip->eg_kon_csm[slot]) { nextlevel |= chip->eg_tl[1] << 3; } /* Envelope off */ if (!kon_event && !chip->eg_ssg_hold_up_latch[slot] && chip->eg_state[slot] != eg_num_attack && eg_off) { nextstate = eg_num_release; nextlevel = 0x3ff; } nextlevel += inc; chip->eg_kon[slot] = chip->eg_kon_latch[slot]; chip->eg_level[slot] = (Bit16u)nextlevel & 0x3ff; chip->eg_state[slot] = nextstate; } void OPN2_EnvelopePrepare(ym3438_t *chip) { Bit8u rate; Bit8u sum; Bit8u inc = 0; Bit32u slot = chip->cycles; Bit8u rate_sel; /* Prepare increment */ rate = (chip->eg_rate << 1) + chip->eg_ksv; if (rate > 0x3f) { rate = 0x3f; } sum = ((rate >> 2) + chip->eg_shift_lock) & 0x0f; if (chip->eg_rate != 0 && chip->eg_quotient == 2) { if (rate < 48) { switch (sum) { case 12: inc = 1; break; case 13: inc = (rate >> 1) & 0x01; break; case 14: inc = rate & 0x01; break; default: break; } } else { inc = eg_stephi[rate & 0x03][chip->eg_timer_low_lock] + (rate >> 2) - 11; if (inc > 4) { inc = 4; } } } chip->eg_inc = inc; chip->eg_ratemax = (rate >> 1) == 0x1f; /* Prepare rate & ksv */ rate_sel = chip->eg_state[slot]; if ((chip->eg_kon[slot] && chip->eg_ssg_repeat_latch[slot]) || (!chip->eg_kon[slot] && chip->eg_kon_latch[slot])) { rate_sel = eg_num_attack; } switch (rate_sel) { case eg_num_attack: chip->eg_rate = chip->ar[slot]; break; case eg_num_decay: chip->eg_rate = chip->dr[slot]; break; case eg_num_sustain: chip->eg_rate = chip->sr[slot]; break; case eg_num_release: chip->eg_rate = (chip->rr[slot] << 1) | 0x01; break; default: break; } chip->eg_ksv = chip->pg_kcode >> (chip->ks[slot] ^ 0x03); if (chip->am[slot]) { chip->eg_lfo_am = chip->lfo_am >> eg_am_shift[chip->ams[chip->channel]]; } else { chip->eg_lfo_am = 0; } /* Delay TL & SL value */ chip->eg_tl[1] = chip->eg_tl[0]; chip->eg_tl[0] = chip->tl[slot]; chip->eg_sl[1] = chip->eg_sl[0]; chip->eg_sl[0] = chip->sl[slot]; } void OPN2_EnvelopeGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 23) % 24; Bit16u level; level = chip->eg_level[slot]; if (chip->eg_ssg_inv[slot]) { /* Inverse */ level = 512 - level; } if (chip->mode_test_21[5]) { level = 0; } level &= 0x3ff; /* Apply AM LFO */ level += chip->eg_lfo_am; /* Apply TL */ if (!(chip->mode_csm && chip->channel == 2 + 1)) { level += chip->eg_tl[0] << 3; } if (level > 0x3ff) { level = 0x3ff; } chip->eg_out[slot] = level; } void OPN2_UpdateLFO(ym3438_t *chip) { if ((chip->lfo_quotient & lfo_cycles[chip->lfo_freq]) == lfo_cycles[chip->lfo_freq]) { chip->lfo_quotient = 0; chip->lfo_cnt++; } else { chip->lfo_quotient += chip->lfo_inc; } chip->lfo_cnt &= chip->lfo_en; } void OPN2_FMPrepare(ym3438_t *chip) { Bit32u slot = (chip->cycles + 6) % 24; Bit32u channel = chip->channel; Bit16s mod, mod1, mod2; Bit32u op = slot / 6; Bit8u connect = chip->connect[channel]; Bit32u prevslot = (chip->cycles + 18) % 24; /* Calculate modulation */ mod1 = mod2 = 0; if (fm_algorithm[op][0][connect]) { mod2 |= chip->fm_op1[channel][0]; } if (fm_algorithm[op][1][connect]) { mod1 |= chip->fm_op1[channel][1]; } if (fm_algorithm[op][2][connect]) { mod1 |= chip->fm_op2[channel]; } if (fm_algorithm[op][3][connect]) { mod2 |= chip->fm_out[prevslot]; } if (fm_algorithm[op][4][connect]) { mod1 |= chip->fm_out[prevslot]; } mod = mod1 + mod2; if (op == 0) { /* Feedback */ mod = mod >> (10 - chip->fb[channel]); if (!chip->fb[channel]) { mod = 0; } } else { mod >>= 1; } chip->fm_mod[slot] = mod; slot = (chip->cycles + 18) % 24; /* OP1 */ if (slot / 6 == 0) { chip->fm_op1[channel][1] = chip->fm_op1[channel][0]; chip->fm_op1[channel][0] = chip->fm_out[slot]; } /* OP2 */ if (slot / 6 == 2) { chip->fm_op2[channel] = chip->fm_out[slot]; } } void OPN2_ChGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 18) % 24; Bit32u channel = chip->channel; Bit32u op = slot / 6; Bit32u test_dac = chip->mode_test_2c[5]; Bit16s acc = chip->ch_acc[channel]; Bit16s add = test_dac; Bit16s sum = 0; if (op == 0 && !test_dac) { acc = 0; } if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) { add += chip->fm_out[slot] >> 5; } sum = acc + add; /* Clamp */ Bit16s channel_maxsample = (1 << (channel_clipbits - 1)) - 1; Bit16s channel_minsample = -(1 << (channel_clipbits - 1)); if (sum > channel_maxsample) { sum = channel_maxsample; } else if(sum < channel_minsample) { sum = channel_minsample; } if (op == 0 || test_dac) { chip->ch_out[channel] = chip->ch_acc[channel]; } chip->ch_acc[channel] = sum; } void OPN2_ChOutput(ym3438_t *chip) { Bit32u cycles = chip->cycles; Bit32u slot = chip->cycles; Bit32u channel = chip->channel; Bit32u test_dac = chip->mode_test_2c[5]; Bit16s out; Bit16s sign; Bit32u out_en; chip->ch_read = chip->ch_lock; if (slot < 12) { /* Ch 4,5,6 */ channel++; } if ((cycles & 3) == 0) { if (!test_dac) { /* Lock value */ chip->ch_lock = chip->ch_out[channel]; } chip->ch_lock_l = chip->pan_l[channel]; chip->ch_lock_r = chip->pan_r[channel]; } /* Ch 6 */ if (((cycles >> 2) == 1 && chip->dacen) || test_dac) { out = (Bit16s)chip->dacdata; out <<= 7; out >>= 7; } else { out = chip->ch_lock; } chip->mol = 0; chip->mor = 0; if (chip_type & ym3438_mode_ym2612) { out_en = ((cycles & 3) == 3) || test_dac; /* YM2612 DAC emulation(not verified) */ sign = out >> 8; if (out >= 0) { out++; sign++; } if (chip->ch_lock_l && out_en) { chip->mol = out; } else { chip->mol = sign; } if (chip->ch_lock_r && out_en) { chip->mor = out; } else { chip->mor = sign; } /* Amplify signal */ chip->mol *= 3; chip->mor *= 3; } else { out_en = ((cycles & 3) != 0) || test_dac; if (chip->ch_lock_l && out_en) { chip->mol = out; } if (chip->ch_lock_r && out_en) { chip->mor = out; } } } void OPN2_FMGenerate(ym3438_t *chip) { Bit32u slot = (chip->cycles + 19) % 24; /* Calculate phase */ Bit16u phase = (chip->fm_mod[slot] + (chip->pg_phase[slot] >> 10)) & 0x3ff; Bit16u quarter; Bit16u level; Bit16s output; if (phase & 0x100) { quarter = (phase ^ 0xff) & 0xff; } else { quarter = phase & 0xff; } level = logsinrom[quarter]; /* Apply envelope */ level += chip->eg_out[slot] << 2; /* Transform */ if (level > 0x1fff) { level = 0x1fff; } output = ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 2) >> (level >> 8); if (phase & 0x200) { output = ((~output) ^ (chip->mode_test_21[4] << 13)) + 1; } else { output = output ^ (chip->mode_test_21[4] << 13); } output <<= 2; output >>= 2; chip->fm_out[slot] = output; } /*OPNMOD: generate ADPCM rhythm*/ void OPNmod_RhythmGenerate(ym3438_t *chip) { Bit32u channel = chip->channel; Bit32s out = 0; Bit8u panl = 0; Bit8u panr = 0; if (chip->cycles < 6 && chip->rhythm_key[channel]) { Bit32u step; Bit8u data; Bit16u end = YM2608_ADPCM_ROM_addr[2 * channel + 1] << 1; panl = chip->rhythm_pan[channel] & 2; panr = chip->rhythm_pan[channel] & 1; /*from MAME*/ chip->rhythm_now_step[channel] += chip->rhythm_step[channel]; if (chip->rhythm_now_step[channel] >= (1 << adpcm_shift)) { step = chip->rhythm_now_step[channel] >> adpcm_shift; chip->rhythm_now_step[channel] &= (1 << adpcm_shift) - 1; do { /* end check */ /* 11-06-2001 JB: corrected comparison. Was > instead of == */ /* YM2610 checks lower 20 bits only, the 4 MSB bits are sample bank */ /* Here we use 1<<21 to compensate for nibble calculations */ if ((chip->rhythm_addr[channel] & ((1 << 21) - 1)) == (end & ((1 << 21) - 1))) { chip->rhythm_key[channel] = 0; goto end; } if (chip->rhythm_addr[channel] & 1) { data = chip->rhythm_data[channel] & 0x0f; } else { chip->rhythm_data[channel] = YM2608_ADPCM_ROM[chip->rhythm_addr[channel] >> 1]; data = (chip->rhythm_data[channel] >> 4) & 0x0f; } chip->rhythm_addr[channel]++; chip->rhythm_adpcm_acc[channel] += adpcm_jedi_table[chip->rhythm_adpcm_step[channel] + data]; /* the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205), it does not saturate (like the msm5218) */ chip->rhythm_adpcm_acc[channel] &= 0xfff; /* extend 12-bit signed int */ if (chip->rhythm_adpcm_acc[channel] & 0x800) { chip->rhythm_adpcm_acc[channel] |= ~0xfff; } chip->rhythm_adpcm_step[channel] += adpcm_step_inc[data & 7]; if (chip->rhythm_adpcm_step[channel] > 48 * 16) { chip->rhythm_adpcm_step[channel] = 48 * 16; } if (chip->rhythm_adpcm_step[channel] < 0) { chip->rhythm_adpcm_step[channel] = 0; } } while (--step); /* calc pcm * volume data */ out = ((chip->rhythm_adpcm_acc[channel] * chip->rhythm_vol_mul[channel]) >> chip->rhythm_vol_shift[channel]) & ~3; /* multiply, shift and mask out 2 LSB bits */ end: chip->rhythml[channel] = panl ? (out >> 1) : 0; chip->rhythmr[channel] = panr ? (out >> 1) : 0; } } } void OPN2_DoTimerA(ym3438_t *chip) { Bit16u time; Bit8u load; load = chip->timer_a_overflow; if (chip->cycles == 2) { /* Lock load value */ load |= (!chip->timer_a_load_lock && chip->timer_a_load); chip->timer_a_load_lock = chip->timer_a_load; if (chip->mode_csm) { /* CSM KeyOn */ chip->mode_kon_csm = load; } else { chip->mode_kon_csm = 0; } } /* Load counter */ if (chip->timer_a_load_latch) { time = chip->timer_a_reg; } else { time = chip->timer_a_cnt; } chip->timer_a_load_latch = load; /* Increase counter */ if ((chip->cycles == 1 && chip->timer_a_load_lock) || chip->mode_test_21[2]) { time++; } /* Set overflow flag */ if (chip->timer_a_reset) { chip->timer_a_reset = 0; chip->timer_a_overflow_flag = 0; } else { chip->timer_a_overflow_flag |= chip->timer_a_overflow & chip->timer_a_enable; } chip->timer_a_overflow = (time >> 10); chip->timer_a_cnt = time & 0x3ff; } void OPN2_DoTimerB(ym3438_t *chip) { Bit16u time; Bit8u load; load = chip->timer_b_overflow; if (chip->cycles == 2) { /* Lock load value */ load |= (!chip->timer_b_load_lock && chip->timer_b_load); chip->timer_b_load_lock = chip->timer_b_load; } /* Load counter */ if (chip->timer_b_load_latch) { time = chip->timer_b_reg; } else { time = chip->timer_b_cnt; } chip->timer_b_load_latch = load; /* Increase counter */ if (chip->cycles == 1) { chip->timer_b_subcnt++; } if ((chip->timer_b_subcnt == 0x10 && chip->timer_b_load_lock) || chip->mode_test_21[2]) { time++; } chip->timer_b_subcnt &= 0x0f; /* Set overflow flag */ if (chip->timer_b_reset) { chip->timer_b_reset = 0; chip->timer_b_overflow_flag = 0; } else { chip->timer_b_overflow_flag |= chip->timer_b_overflow & chip->timer_b_enable; } chip->timer_b_overflow = (time >> 8); chip->timer_b_cnt = time & 0xff; } void OPN2_KeyOn(ym3438_t*chip) { Bit32u slot = chip->cycles; Bit32u chan = chip->channel; /* Key On */ chip->eg_kon_latch[slot] = chip->mode_kon[slot]; chip->eg_kon_csm[slot] = 0; if (chip->channel == 2 && chip->mode_kon_csm) { /* CSM Key On */ chip->eg_kon_latch[slot] = 1; chip->eg_kon_csm[slot] = 1; } if (chip->cycles == chip->mode_kon_channel) { /* OP1 */ chip->mode_kon[chan] = chip->mode_kon_operator[0]; /* OP2 */ chip->mode_kon[chan + 12] = chip->mode_kon_operator[1]; /* OP3 */ chip->mode_kon[chan + 6] = chip->mode_kon_operator[2]; /* OP4 */ chip->mode_kon[chan + 18] = chip->mode_kon_operator[3]; } } void OPN2_Reset(ym3438_t *chip, Bit32u clock, const struct OPN2mod_psg_callbacks *psg, void *psgdata) { Bit32u i; memset(chip, 0, sizeof(ym3438_t)); for (i = 0; i < 24; i++) { chip->eg_out[i] = 0x3ff; chip->eg_level[i] = 0x3ff; chip->eg_state[i] = eg_num_release; chip->multi[i] = 1; } for (i = 0; i < 6; i++) { chip->pan_l[i] = 1; chip->pan_r[i] = 1; } /*OPN-MOD: initialize and connect PSG*/ chip->psg = psg; chip->psgdata = psgdata; psg->Reset(psgdata); psg->SetClock(psgdata, clock / 4); /*OPN-MOD: initialize rhythm*/ for (i = 0; i < 6; i++) { chip->rhythm_pan[i] = 3; chip->rhythm_step[i] = (Bit32u)((1 << adpcm_shift) / ((i < 4) ? 3.0 : 6.0)); OPNmod_RhythmUpdateVolume(chip, i); } } void OPN2_SetChipType(Bit32u type) { chip_type = type; } void OPN2_Clock(ym3438_t *chip, Bit16s *buffer) { Bit32u slot = chip->cycles; Bit16s *rhythml = &chip->rhythml[chip->channel]; Bit16s *rhythmr = &chip->rhythmr[chip->channel]; chip->lfo_inc = chip->mode_test_21[1]; chip->pg_read >>= 1; chip->eg_read[1] >>= 1; chip->eg_cycle++; /* Lock envelope generator timer value */ if (chip->cycles == 1 && chip->eg_quotient == 2) { if (chip->eg_cycle_stop) { chip->eg_shift_lock = 0; } else { chip->eg_shift_lock = chip->eg_shift + 1; } chip->eg_timer_low_lock = chip->eg_timer & 0x03; } /* Cycle specific functions */ switch (chip->cycles) { case 0: chip->lfo_pm = chip->lfo_cnt >> 2; if (chip->lfo_cnt & 0x40) { chip->lfo_am = chip->lfo_cnt & 0x3f; } else { chip->lfo_am = chip->lfo_cnt ^ 0x3f; } chip->lfo_am <<= 1; break; case 1: chip->eg_quotient++; chip->eg_quotient %= 3; chip->eg_cycle = 0; chip->eg_cycle_stop = 1; chip->eg_shift = 0; chip->eg_timer_inc |= chip->eg_quotient >> 1; chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; chip->eg_timer_inc = chip->eg_timer >> 12; chip->eg_timer &= 0xfff; break; case 2: chip->pg_read = chip->pg_phase[21] & 0x3ff; chip->eg_read[1] = chip->eg_out[0]; break; case 13: chip->eg_cycle = 0; chip->eg_cycle_stop = 1; chip->eg_shift = 0; chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; chip->eg_timer_inc = chip->eg_timer >> 12; chip->eg_timer &= 0xfff; break; case 23: chip->lfo_inc |= 1; break; } chip->eg_timer &= ~(chip->mode_test_21[5] << chip->eg_cycle); if (((chip->eg_timer >> chip->eg_cycle) | (chip->pin_test_in & chip->eg_custom_timer)) & chip->eg_cycle_stop) { chip->eg_shift = chip->eg_cycle; chip->eg_cycle_stop = 0; } OPN2_DoIO(chip); OPN2_DoTimerA(chip); OPN2_DoTimerB(chip); OPN2_KeyOn(chip); OPN2_ChOutput(chip); OPN2_ChGenerate(chip); OPN2_FMPrepare(chip); OPN2_FMGenerate(chip); OPNmod_RhythmGenerate(chip); OPN2_PhaseGenerate(chip); OPN2_PhaseCalcIncrement(chip); OPN2_EnvelopeADSR(chip); OPN2_EnvelopeGenerate(chip); OPN2_EnvelopeSSGEG(chip); OPN2_EnvelopePrepare(chip); /* Prepare fnum & block */ if (chip->mode_ch3) { /* Channel 3 special mode */ switch (slot) { case 1: /* OP1 */ chip->pg_fnum = chip->fnum_3ch[1]; chip->pg_block = chip->block_3ch[1]; chip->pg_kcode = chip->kcode_3ch[1]; break; case 7: /* OP3 */ chip->pg_fnum = chip->fnum_3ch[0]; chip->pg_block = chip->block_3ch[0]; chip->pg_kcode = chip->kcode_3ch[0]; break; case 13: /* OP2 */ chip->pg_fnum = chip->fnum_3ch[2]; chip->pg_block = chip->block_3ch[2]; chip->pg_kcode = chip->kcode_3ch[2]; break; case 19: /* OP4 */ default: chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; chip->pg_block = chip->block[(chip->channel + 1) % 6]; chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; break; } } else { chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; chip->pg_block = chip->block[(chip->channel + 1) % 6]; chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; } OPN2_UpdateLFO(chip); OPN2_DoRegWrite(chip); chip->cycles = (chip->cycles + 1) % 24; chip->channel = chip->cycles % 6; buffer[0] = chip->mol; buffer[1] = chip->mor; /*OPN-MOD: Rhythm output*/ buffer[2] = *rhythml; buffer[3] = *rhythmr; if (chip->status_time) chip->status_time--; } void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) { port &= 3; chip->write_data = ((port << 7) & 0x100) | data; if (port & 1) { /* Data */ chip->write_d |= 1; } else { /* Address */ chip->write_a |= 1; } } void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) { chip->pin_test_in = value & 1; } Bit32u OPN2_ReadTestPin(ym3438_t *chip) { if (!chip->mode_test_2c[7]) { return 0; } return chip->cycles == 23; } Bit32u OPN2_ReadIRQPin(ym3438_t *chip) { return chip->timer_a_overflow_flag | chip->timer_b_overflow_flag; } Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) { if ((port & 3) == 0 || (chip_type & ym3438_mode_readmode)) { if (chip->mode_test_21[6]) { /* Read test data */ Bit32u slot = (chip->cycles + 18) % 24; Bit16u testdata = ((chip->pg_read & 0x01) << 15) | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14); if (chip->mode_test_2c[4]) { testdata |= chip->ch_read & 0x1ff; } else { testdata |= chip->fm_out[slot] & 0x3fff; } if (chip->mode_test_21[7]) { chip->status = testdata & 0xff; } else { chip->status = testdata >> 8; } } else { chip->status = (chip->busy << 7) | (chip->timer_b_overflow_flag << 1) | chip->timer_a_overflow_flag; } if (chip_type & ym3438_mode_ym2612) { chip->status_time = 300000; } else { chip->status_time = 40000000; } } /*OPN-MOD: read from PSG*/ if ((port & 3) == 1) { if (chip->address < 16) { return chip->psg->Read(chip->psgdata); } else if(chip->address == 0xff) { return 1; /* ID code */ } } if (chip->status_time) { return chip->status; } return 0; } void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data) { Bit64u time1, time2; Bit16s buffer[4]; Bit64u skip; if (chip->writebuf[chip->writebuf_last].port & 0x04) { OPN2_Write(chip, chip->writebuf[chip->writebuf_last].port & 0X03, chip->writebuf[chip->writebuf_last].data); chip->writebuf_cur = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; skip = chip->writebuf[chip->writebuf_last].time - chip->writebuf_samplecnt; chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time; while (skip--) { OPN2_Clock(chip, buffer); } } chip->writebuf[chip->writebuf_last].port = (port & 0x03) | 0x04; chip->writebuf[chip->writebuf_last].data = data; time1 = chip->writebuf_lasttime + OPN_WRITEBUF_DELAY; time2 = chip->writebuf_samplecnt; if (time1 < time2) { time1 = time2; } chip->writebuf[chip->writebuf_last].time = time1; chip->writebuf_lasttime = time1; chip->writebuf_last = (chip->writebuf_last + 1) % OPN_WRITEBUF_SIZE; } void OPN2_Generate(ym3438_t *chip, sample *samples) { Bit32u i; Bit16s buffer[4]; samples[0] = 0; samples[1] = 0; for (i = 0; i < 24; i++) { OPN2_Clock(chip, buffer); samples[0] += buffer[0] * 11; samples[1] += buffer[1] * 11; /*OPN-MOD: mix rhythm samples*/ samples[0] += buffer[2]; samples[1] += buffer[3]; while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt) { if (!(chip->writebuf[chip->writebuf_cur].port & 0x04)) { break; } chip->writebuf[chip->writebuf_cur].port &= 0x03; OPN2_Write(chip, chip->writebuf[chip->writebuf_cur].port, chip->writebuf[chip->writebuf_cur].data); chip->writebuf_cur = (chip->writebuf_cur + 1) % OPN_WRITEBUF_SIZE; } chip->writebuf_samplecnt++; } } BambooTracker-0.3.5/BambooTracker/chips/nuked/ym3438.h000066400000000000000000000154461362177441300223230ustar00rootroot00000000000000/* * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) * Copyright (C) 2019 Jean Pierre Cimalando * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * * Nuked OPN2-MOD emulator, with OPNA functionality added. * Thanks: * Silicon Pr0n: * Yamaha YM3438 decap and die shot(digshadow). * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): * OPL2 ROMs. * * version: 1.0.9 * * OPN-MOD additions: * - Jean Pierre Cimalando 2019-04-06: add SSG control interface * - Jean Pierre Cimalando 2019-04-06: add 6-channel FM flag * - Jean Pierre Cimalando 2019-04-06: add ADPCM rhythm channels * - Jean Pierre Cimalando 2019-04-07: raise the channel clipping threshold */ #ifndef YM3438_H #define YM3438_H #include "../chip_def.h" #ifdef __cplusplus extern "C" { #endif /*EXTRA*/ #define RSM_FRAC 10 #define OPN_WRITEBUF_SIZE 2048 #define OPN_WRITEBUF_DELAY 15 enum { ym3438_mode_ym2612 = 0x01, /* Enables YM2612 emulation (MD1, MD2 VA2) */ ym3438_mode_readmode = 0x02 /* Enables status read on any port (TeraDrive, MD1 VA7, MD2, etc) */ }; #include typedef uintptr_t Bitu; typedef intptr_t Bits; typedef uint64_t Bit64u; typedef int64_t Bit64s; typedef uint32_t Bit32u; typedef int32_t Bit32s; typedef uint16_t Bit16u; typedef int16_t Bit16s; typedef uint8_t Bit8u; typedef int8_t Bit8s; /*EXTRA*/ typedef struct _opn2_writebuf { Bit64u time; Bit8u port; Bit8u data; Bit8u reserved[6]; } opn2_writebuf; typedef struct { Bit32u cycles; Bit32u channel; Bit16s mol, mor; /*OPN-MOD: Rhythm channel outputs*/ Bit16s rhythml[6]; Bit16s rhythmr[6]; /* IO */ Bit16u write_data; Bit8u write_a; Bit8u write_d; Bit8u write_a_en; Bit8u write_d_en; Bit8u write_busy; Bit8u write_busy_cnt; Bit8u write_fm_address; Bit8u write_fm_data; Bit8u write_fm_mode_a; Bit16u address; Bit8u data; Bit8u pin_test_in; Bit8u pin_irq; Bit8u busy; /* LFO */ Bit8u lfo_en; Bit8u lfo_freq; Bit8u lfo_pm; Bit8u lfo_am; Bit8u lfo_cnt; Bit8u lfo_inc; Bit8u lfo_quotient; /* Phase generator */ Bit16u pg_fnum; Bit8u pg_block; Bit8u pg_kcode; Bit32u pg_inc[24]; Bit32u pg_phase[24]; Bit8u pg_reset[24]; Bit32u pg_read; /* Envelope generator */ Bit8u eg_cycle; Bit8u eg_cycle_stop; Bit8u eg_shift; Bit8u eg_shift_lock; Bit8u eg_timer_low_lock; Bit16u eg_timer; Bit8u eg_timer_inc; Bit16u eg_quotient; Bit8u eg_custom_timer; Bit8u eg_rate; Bit8u eg_ksv; Bit8u eg_inc; Bit8u eg_ratemax; Bit8u eg_sl[2]; Bit8u eg_lfo_am; Bit8u eg_tl[2]; Bit8u eg_state[24]; Bit16u eg_level[24]; Bit16u eg_out[24]; Bit8u eg_kon[24]; Bit8u eg_kon_csm[24]; Bit8u eg_kon_latch[24]; Bit8u eg_csm_mode[24]; Bit8u eg_ssg_enable[24]; Bit8u eg_ssg_pgrst_latch[24]; Bit8u eg_ssg_repeat_latch[24]; Bit8u eg_ssg_hold_up_latch[24]; Bit8u eg_ssg_dir[24]; Bit8u eg_ssg_inv[24]; Bit32u eg_read[2]; Bit8u eg_read_inc; /* FM */ Bit16s fm_op1[6][2]; Bit16s fm_op2[6]; Bit16s fm_out[24]; Bit16u fm_mod[24]; /* Channel */ Bit16s ch_acc[6]; Bit16s ch_out[6]; Bit16s ch_lock; Bit8u ch_lock_l; Bit8u ch_lock_r; Bit16s ch_read; /*OPNA: Rhythm channel*/ Bit8u rhythm_tl; Bit8u rhythm_key[6]; Bit8u rhythm_pan[6]; Bit8u rhythm_level[6]; Bit16u rhythm_addr[6]; Bit8u rhythm_data[6]; Bit32u rhythm_step[6]; Bit32u rhythm_now_step[6]; Bit32s rhythm_adpcm_step[6]; Bit32s rhythm_adpcm_acc[6]; Bit8s rhythm_vol_mul[6]; Bit8u rhythm_vol_shift[6]; /* Timer */ Bit16u timer_a_cnt; Bit16u timer_a_reg; Bit8u timer_a_load_lock; Bit8u timer_a_load; Bit8u timer_a_enable; Bit8u timer_a_reset; Bit8u timer_a_load_latch; Bit8u timer_a_overflow_flag; Bit8u timer_a_overflow; Bit16u timer_b_cnt; Bit8u timer_b_subcnt; Bit16u timer_b_reg; Bit8u timer_b_load_lock; Bit8u timer_b_load; Bit8u timer_b_enable; Bit8u timer_b_reset; Bit8u timer_b_load_latch; Bit8u timer_b_overflow_flag; Bit8u timer_b_overflow; /* Register set */ Bit8u mode_test_21[8]; Bit8u mode_test_2c[8]; Bit8u mode_ch3; Bit8u mode_kon_channel; Bit8u mode_kon_operator[4]; Bit8u mode_kon[24]; Bit8u mode_csm; Bit8u mode_kon_csm; Bit8u dacen; Bit16s dacdata; /*OPN-MOD: 6 FM channel flag*/ Bit8u mode_fm6ch; Bit8u ks[24]; Bit8u ar[24]; Bit8u sr[24]; Bit8u dt[24]; Bit8u multi[24]; Bit8u sl[24]; Bit8u rr[24]; Bit8u dr[24]; Bit8u am[24]; Bit8u tl[24]; Bit8u ssg_eg[24]; Bit16u fnum[6]; Bit8u block[6]; Bit8u kcode[6]; Bit16u fnum_3ch[6]; Bit8u block_3ch[6]; Bit8u kcode_3ch[6]; Bit8u reg_a4; Bit8u reg_ac; Bit8u connect[6]; Bit8u fb[6]; Bit8u pan_l[6], pan_r[6]; Bit8u ams[6]; Bit8u pms[6]; Bit8u status; Bit32u status_time; /*EXTRA*/ Bit64u writebuf_samplecnt; Bit32u writebuf_cur; Bit32u writebuf_last; Bit64u writebuf_lasttime; opn2_writebuf writebuf[OPN_WRITEBUF_SIZE]; /*OPN-MOD*/ const struct OPN2mod_psg_callbacks *psg; void *psgdata; } ym3438_t; void OPN2_Reset(ym3438_t *chip, Bit32u clock, const struct OPN2mod_psg_callbacks *psg, void *psgdata); void OPN2_SetChipType(Bit32u type); void OPN2_Clock(ym3438_t *chip, Bit16s *buffer); void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); Bit32u OPN2_ReadTestPin(ym3438_t *chip); Bit32u OPN2_ReadIRQPin(ym3438_t *chip); Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); /*EXTRA*/ void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data); void OPN2_Generate(ym3438_t *chip, sample *samples); /*OPN-MOD*/ struct OPN2mod_psg_callbacks { void (*SetClock)(void *param, int clock); void (*Write)(void *param, int address, int data); int (*Read)(void *param); void (*Reset)(void *param); }; #ifdef __cplusplus } #endif #endif BambooTracker-0.3.5/BambooTracker/chips/opna.cpp000066400000000000000000000131561362177441300215320ustar00rootroot00000000000000#include "opna.hpp" #include #include #include "chip_misc.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus #include "mame/2608intf.h" #include "nuked/nuke2608intf.h" #ifdef __cplusplus } #endif // __cplusplus namespace chip { size_t OPNA::count_ = 0; const double OPNA::VOL_REDUC = 7.5; OPNA::OPNA(Emu emu, int clock, int rate, size_t maxDuration, std::unique_ptr fmResampler, std::unique_ptr ssgResampler, std::shared_ptr exportContainer) : Chip(count_++, clock, rate, 110933, maxDuration, std::move(fmResampler), std::move(ssgResampler), // autoRate = 110933: FM internal rate exportContainer), scciManager_(nullptr), scciChip_(nullptr), c86ctlBase_(nullptr), c86ctlRC_(nullptr), c86ctlGm_(nullptr) { switch (emu) { default: fprintf(stderr, "Unknown emulator choice. Using the default.\n"); /* fall through */ case Emu::Mame: fprintf(stderr, "Using emulator: MAME YM2608\n"); intf_ = &mame_intf2608; break; case Emu::Nuked: fprintf(stderr, "Using emulator: Nuked OPN-Mod\n"); intf_ = &nuked_intf2608; break; } funcSetRate(rate); uint8_t EmuCore = 0; intf_->set_ay_emu_core(EmuCore); uint8_t AYDisable = 0; // Enable uint8_t AYFlags = 0; // None internalRate_[FM] = intf_->device_start(id_, clock, AYDisable, AYFlags, reinterpret_cast(&internalRate_[SSG])); initResampler(); setVolumeFM(0); setVolumeSSG(0); reset(); } OPNA::~OPNA() { intf_->device_stop(id_); --count_; useSCCI(nullptr); useC86CTL(nullptr); } void OPNA::reset() { std::lock_guard lg(mutex_); intf_->device_reset(id_); if (scciChip_) scciChip_->init(); if (c86ctlRC_) c86ctlRC_->resetChip(); } void OPNA::setRegister(uint32_t offset, uint8_t value) { std::lock_guard lg(mutex_); if (needSampleGen_) { if (offset & 0x100) { intf_->control_port_b_w(id_, 2, offset & 0xff); intf_->data_port_b_w(id_, 3, value & 0xff); } else { intf_->control_port_a_w(id_, 0, offset & 0xff); intf_->data_port_a_w(id_, 1, value & 0xff); } } if (scciChip_) scciChip_->setRegister(offset, value); if (c86ctlRC_) c86ctlRC_->out(offset, value); if (exCntr_) exCntr_->recordRegisterChange(offset, value); } uint8_t OPNA::getRegister(uint32_t offset) const { if (offset & 0x100) { intf_->control_port_b_w(id_, 2, offset & 0xff); } else { intf_->control_port_a_w(id_, 0, offset & 0xff); } return intf_->read_port_r(id_, 1); } void OPNA::setVolumeFM(double dB) { std::lock_guard lg(mutex_); volumeRatio_[FM] = std::pow(10.0, (dB - VOL_REDUC) / 20.0); } void OPNA::setVolumeSSG(double dB) { std::lock_guard lg(mutex_); volumeRatio_[SSG] = std::pow(10.0, (dB - VOL_REDUC) / 20.0); if (c86ctlGm_) { // NOTE: estimate SSG volume roughly uint8_t vol = static_cast(std::round((dB < -3.0) ? (2.5 * dB + 45.5) : (7. * dB + 59.))); c86ctlGm_->setSSGVolume(vol); } } void OPNA::mix(int16_t* stream, size_t nSamples) { std::lock_guard lg(mutex_); if (needSampleGen_) { sample **bufFM, **bufSSG; // Set FM buffer if (internalRate_[FM] == rate_) { intf_->stream_update(id_, buffer_[FM], nSamples); bufFM = buffer_[FM]; } else { size_t intrSize = resampler_[FM]->calculateInternalSampleSize(nSamples); intf_->stream_update(id_, buffer_[FM], intrSize); bufFM = resampler_[FM]->interpolate(buffer_[FM], nSamples, intrSize); } // Set SSG buffer if (internalRate_[SSG] == rate_) { intf_->stream_update_ay(id_, buffer_[SSG], nSamples); bufSSG = buffer_[SSG]; } else { size_t intrSize = resampler_[SSG]->calculateInternalSampleSize(nSamples); intf_->stream_update_ay(id_, buffer_[SSG], intrSize); bufSSG = resampler_[SSG]->interpolate(buffer_[SSG], nSamples, intrSize); } int16_t* p = stream; for (size_t i = 0; i < nSamples; ++i) { for (int pan = LEFT; pan <= RIGHT; ++pan) { double s = volumeRatio_[FM] * bufFM[pan][i] + volumeRatio_[SSG] * bufSSG[pan][i]; *p++ = static_cast(clamp(s * masterVolumeRatio_, -32768.0, 32767.0)); } } } if (exCntr_) exCntr_->recordStream(stream, nSamples); } void OPNA::useSCCI(scci::SoundInterfaceManager* manager) { if (manager) { scciManager_ = manager; scciManager_->initializeInstance(); scciManager_->reset(); scciChip_ = scciManager_->getSoundChip(scci::SC_TYPE_YM2608, scci::SC_CLOCK_7987200); if (!scciChip_) { scciManager_->releaseInstance(); scciManager_ = nullptr; } } else { if (!scciChip_) return; scciManager_->releaseSoundChip(scciChip_); scciChip_ = nullptr; scciManager_->releaseInstance(); scciManager_ = nullptr; } } bool OPNA::isUsedSCCI() const { return (scciManager_ != nullptr); } void OPNA::useC86CTL(C86ctlBase* base) { if (!base || base->isEmpty()) { if (!c86ctlBase_) return; c86ctlRC_->resetChip(); c86ctlGm_.reset(); c86ctlRC_.reset(); c86ctlBase_->deinitialize(); } else { c86ctlBase_.reset(base); c86ctlBase_->initialize(); int nChip = c86ctlBase_->getNumberOfChip(); for (int i = 0; i < nChip; ++i) { C86ctlRealChip* rc = c86ctlBase_->getChipInterface(i); if (rc) { c86ctlRC_.reset(rc); c86ctlRC_->resetChip(); if (C86ctlGimic* gm = c86ctlRC_->queryInterface()) { c86ctlGm_.reset(gm); return; } c86ctlRC_.reset(); } } base->deinitialize(); } c86ctlBase_.reset(); } bool OPNA::isUsedC86CTL() const { return (c86ctlBase_ != nullptr); } } BambooTracker-0.3.5/BambooTracker/chips/opna.hpp000066400000000000000000000026261362177441300215370ustar00rootroot00000000000000#pragma once #include "chip.hpp" #include #include "chip_misc.h" #include "scci/scci.h" #include "scci/SCCIDefines.h" #include "c86ctl/c86ctl_wrapper.hpp" namespace chip { class OPNA : public Chip { public: // [rate] // 0 = rate is 55466 (FM synthesis rate when clock is 3993600 * 2) OPNA(Emu emu, int clock, int rate, size_t maxDuration, std::unique_ptr fmResampler = std::make_unique(), std::unique_ptr ssgResampler = std::make_unique(), std::shared_ptr exportContainer = nullptr); ~OPNA() override; void reset() override; void setRegister(uint32_t offset, uint8_t value) override; uint8_t getRegister(uint32_t offset) const override; void setVolumeFM(double dB); void setVolumeSSG(double dB); void mix(int16_t* stream, size_t nSamples) override; void useSCCI(scci::SoundInterfaceManager* manager); bool isUsedSCCI() const; void useC86CTL(C86ctlBase* base); bool isUsedC86CTL() const; private: static size_t count_; intf2608* intf_; // For SCCI scci::SoundInterfaceManager* scciManager_; scci::SoundChip* scciChip_; // For C86CTL std::unique_ptr c86ctlBase_; std::unique_ptr c86ctlRC_; std::unique_ptr c86ctlGm_; static const double VOL_REDUC; enum SoundSource : int { FM = 0, SSG = 1 }; }; } BambooTracker-0.3.5/BambooTracker/chips/resampler.cpp000066400000000000000000000063741362177441300225730ustar00rootroot00000000000000#include "resampler.hpp" #include "chip_misc.h" namespace chip { AbstractResampler::AbstractResampler() { for (int pan = LEFT; pan <= RIGHT; ++pan) { destBuf_[pan] = new sample[SMPL_BUF_SIZE_](); } } AbstractResampler::~AbstractResampler() { for (int pan = LEFT; pan <= RIGHT; ++pan) { delete[] destBuf_[pan]; } } void AbstractResampler::init(int srcRate, int destRate, size_t maxDuration) { srcRate_ = srcRate; maxDuration_ = maxDuration; destRate_ = destRate; updateRateRatio(); } void AbstractResampler::setDestributionRate(int destRate) { destRate_ = destRate; updateRateRatio(); } void AbstractResampler::setMaxDuration(size_t maxDuration) { maxDuration_ = maxDuration; } /****************************************/ sample** LinearResampler::interpolate(sample** src, size_t nSamples, size_t intrSize) { (void)intrSize; // Linear interplation for (int pan = LEFT; pan <= RIGHT; ++pan) { for (size_t n = 0; n < nSamples; ++n) { float curnf = n * rateRatio_; int curni = static_cast(curnf); float sub = curnf - curni; if (sub) { destBuf_[pan][n] = static_cast(src[pan][curni] + (src[pan][curni + 1] - src[pan][curni]) * sub); } else /* if (sub == 0) */ { destBuf_[pan][n] = src[pan][curni]; } } } return destBuf_; } /****************************************/ const float SincResampler::F_PI_ = 3.14159265f; const int SincResampler::SINC_OFFSET_ = 16; void SincResampler::init(int srcRate, int destRate, size_t maxDuration) { AbstractResampler::init(srcRate, destRate, maxDuration); initSincTables(); } void SincResampler::setDestributionRate(int destRate) { AbstractResampler::setDestributionRate(destRate); initSincTables(); } void SincResampler::setMaxDuration(size_t maxDuration) { AbstractResampler::setMaxDuration(maxDuration); initSincTables(); } sample** SincResampler::interpolate(sample** src, size_t nSamples, size_t intrSize) { // Sinc interpolation size_t offsetx2 = SINC_OFFSET_ << 1; for (int pan = LEFT; pan <= RIGHT; ++pan) { for (size_t n = 0; n < nSamples; ++n) { size_t seg = n * offsetx2; int curn = static_cast(n * rateRatio_); int k = curn - SINC_OFFSET_; if (k < 0) k = 0; int end = curn + SINC_OFFSET_; if (static_cast(end) > intrSize) end = static_cast(intrSize); sample samp = 0; for (; k < end; ++k) { samp += static_cast(src[pan][k] * sincTable_[seg + SINC_OFFSET_ + (k - curn)]); } destBuf_[pan][n] = samp; } } return destBuf_; } void SincResampler::initSincTables() { size_t maxSamples = destRate_ * maxDuration_ / 1000; if (srcRate_ != destRate_) { size_t intrSize = calculateInternalSampleSize(maxSamples); size_t offsetx2 = SINC_OFFSET_ << 1; sincTable_.resize(maxSamples * offsetx2); for (size_t n = 0; n < maxSamples; ++n) { size_t seg = n * offsetx2; float rcurn = n * rateRatio_; int curn = static_cast(rcurn); int k = curn - SINC_OFFSET_; if (k < 0) k = 0; int end = curn + SINC_OFFSET_; if (static_cast(end) > intrSize) end = static_cast(intrSize); for (; k < end; ++k) { sincTable_[seg + SINC_OFFSET_ + (k - curn)] = sinc(F_PI_ * (rcurn - k)); } } } } } BambooTracker-0.3.5/BambooTracker/chips/resampler.hpp000066400000000000000000000030071362177441300225660ustar00rootroot00000000000000#pragma once #include "chip_def.h" #include #include #include namespace chip { class AbstractResampler { public: AbstractResampler(); virtual ~AbstractResampler(); virtual void init(int srcRate, int destRate, size_t maxDuration); virtual void setDestributionRate(int destRate); virtual void setMaxDuration(size_t maxDuration); virtual sample** interpolate(sample** src, size_t nSamples, size_t intrSize) = 0; inline size_t calculateInternalSampleSize(size_t nSamples) { float f = nSamples * rateRatio_; size_t i = static_cast(f); return ((f - i) ? (i + 1) : i); } protected: inline void updateRateRatio() { rateRatio_ = static_cast(srcRate_) / destRate_; } protected: int srcRate_, destRate_; size_t maxDuration_; float rateRatio_; sample* destBuf_[2]; }; class LinearResampler : public AbstractResampler { public: sample** interpolate(sample** src, size_t nSamples, size_t intrSize) override; }; class SincResampler : public AbstractResampler { public: void init(int srcRate, int destRate, size_t maxDuration) override; void setDestributionRate(int destRate) override; void setMaxDuration(size_t maxDuration) override; sample** interpolate(sample** src, size_t nSamples, size_t intrSize) override; private: std::vector sincTable_; static const float F_PI_; static const int SINC_OFFSET_; void initSincTables(); static inline float sinc(float x) { return ((!x) ? 1.0f : (std::sin(x) / x)); } }; } BambooTracker-0.3.5/BambooTracker/chips/scci/000077500000000000000000000000001362177441300210045ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/chips/scci/SCCIDefines.h000066400000000000000000000100401362177441300231670ustar00rootroot00000000000000//---------------------------------------------------------------------- // SCCI Sound Interfaces defines //---------------------------------------------------------------------- #pragma once namespace scci { // Sound chip list enum SC_CHIP_TYPE { SC_TYPE_NONE = 0, SC_TYPE_YM2608, SC_TYPE_YM2151, SC_TYPE_YM2610, SC_TYPE_YM2203, SC_TYPE_YM2612, SC_TYPE_AY8910, SC_TYPE_SN76489, SC_TYPE_YM3812, SC_TYPE_YMF262, SC_TYPE_YM2413, SC_TYPE_YM3526, SC_TYPE_YMF288, SC_TYPE_SCC, SC_TYPE_SCCS, SC_TYPE_Y8950, SC_TYPE_YM2164, // OPP:OPMとはハードウェアLFOの制御が違う SC_TYPE_YM2414, // OPZ:OPMとピンコンパチ SC_TYPE_AY8930, // APSG:拡張PSG SC_TYPE_YM2149, // SSG:PSGとはDACが違う(YM3439とは同一とみていいと思う) SC_TYPE_YMZ294, // SSGL:SSGとはDACが違う(YMZ284とは同一とみていいと思う) SC_TYPE_SN76496, // DCSG:76489とはノイズジェネレータの生成式が違う SC_TYPE_YM2420, // OPLL2:OPLLとはFnumの設定方法が違う。音は同じ。 SC_TYPE_YMF281, // OPLLP:OPLLとは内蔵ROM音色が違う。制御は同じ。 SC_TYPE_YMF276, // OPN2L:OPN2/OPN2CとはDACが違う SC_TYPE_YM2610B, // OPNB-B:OPNBとはFM部のch数が違う。 SC_TYPE_YMF286, // OPNB-C:OPNBとはDACが違う。 SC_TYPE_YM2602, // 315-5124: 76489/76496とはノイズジェネレータの生成式が違う。POWON時に発振しない。 SC_TYPE_UM3567, // OPLLのコピー品(だけどDIP24なのでそのままリプレースできない) SC_TYPE_YMF274, // OPL4:試作未定 SC_TYPE_YM3806, // OPQ:試作予定 SC_TYPE_YM2163, // DSG:試作中 SC_TYPE_YM7129, // OPK2:試作中 SC_TYPE_YMZ280, // PCM8:ADPCM8ch:試作予定 SC_TYPE_YMZ705, // SSGS:SSG*2set+ADPCM8ch:試作中 SC_TYPE_YMZ735, // FMS:FM8ch+ADPCM8ch:試作中 SC_TYPE_YM2423, // YM2413の音色違い SC_TYPE_SPC700, // SPC700 SC_TYPE_NBV4, // NBV4用 SC_TYPE_AYB02, // AYB02用 SC_TYPE_8253, // i8253(及び互換チップ用) SC_TYPE_315_5124, // DCSG互換チップ SC_TYPE_SPPCM, // SPPCM SC_TYPE_C140, // NAMCO C140(SPPCMデバイス) SC_TYPE_SEGAPCM, // SEGAPCM(SPPCMデバイス) SC_TYPE_SPW, // SPW SC_TYPE_SAM2695, // SAM2695 SC_TYPE_MIDI, // MIDIインターフェース SC_TYPE_MAX, // 使用可能デバイスMAX値 // 以降は、専用ハード用 // 実験ハード用 SC_TYPE_OTHER = 1000, // その他デバイス用、アドレスがA0-A3で動作する SC_TYPE_UNKNOWN, // 開発デバイス向け SC_TYPE_YMF825, // YMF825(暫定) }; // Sound chip clock list enum SC_CHIP_CLOCK { SC_CLOCK_NONE = 0, SC_CLOCK_1789773 = 1789773, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_1996800 = 1996800, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_2000000 = 2000000, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_2048000 = 2048000, // SSGLP(4096/2|6144/3) SC_CLOCK_3579545 = 3579545, // SSG,OPN,OPM,SN76489 etc SC_CLOCK_3993600 = 3993600, // OPN(88) SC_CLOCK_4000000 = 4000000, // SSF,OPN,OPM etc SC_CLOCK_7159090 = 7159090, // OPN,OPNA,OPNB,OPN2,OPN3L etc SC_CLOCK_7670454 = 7670454, // YM-2612 etc SC_CLOCK_7987200 = 7987200, // OPNA(88) SC_CLOCK_8000000 = 8000000, // OPNB etc SC_CLOCK_10738635 = 10738635, // 315-5124 SC_CLOCK_12500000 = 12500000, // RF5C164 SC_CLOCK_14318180 = 14318180, // OPL2 SC_CLOCK_16934400 = 16934400, // YMF271 SC_CLOCK_23011361 = 23011361, // PWM }; // Sound chip location enum SC_CHIP_LOCATION { SC_LOCATION_MONO = 0, SC_LOCATION_LEFT = 1, SC_LOCATION_RIGHT = 2, SC_LOCATION_STEREO = 3 }; // mode defines #define SC_MODE_ASYNC (0x00000000) #define SC_MODE_SYNC (0x00000001) // sound chip Acquisition mode defines #define SC_ACQUISITION_MODE_NEAR (0x00000000) #define SC_ACQUISITION_MODE_MATCH (0x00000001) #define SC_WAIT_REG (0xffffffff) // ウェイとコマンド送信(データは送信するコマンド数) #define SC_FLUSH_REG (0xfffffffe) // 書き込みデータフラッシュ待ち #define SC_DIRECT_BUS (0x80000000) // アドレスバスダイレクトモード } BambooTracker-0.3.5/BambooTracker/chips/scci/scci.h000066400000000000000000000131731362177441300221030ustar00rootroot00000000000000//---------------------------------------------------------------------- // Sound Chip common Interface //---------------------------------------------------------------------- #pragma once /*#include */ #include #ifndef _WIN32 #define __stdcall #endif namespace scci { using DWORD = uint32_t; using BOOL = bool; using BYTE = uint8_t; // Sound Interface Infomation typedef struct { char cInterfaceName[64]; // Interface Name int iSoundChipCount; // Sound Chip Count; } SCCI_INTERFACE_INFO; // Sound Chip Infomation typedef struct { char cSoundChipName[64]; // Sound Chip Name int iSoundChip; // Sound Chip ID int iCompatibleSoundChip[2]; // Compatible Sound Chip ID DWORD dClock; // Sound Chip clock DWORD dCompatibleClock[2]; // Sound Chip clock BOOL bIsUsed; // Sound Chip Used Check DWORD dBusID; // 接続バスID DWORD dSoundLocation; // サウンドロケーション } SCCI_SOUND_CHIP_INFO; class SoundInterfaceManager; class SoundInterface; class SoundChip; //---------------------------------------- // Sound Interface Manager //---------------------------------------- class SoundInterfaceManager{ public: // ---------- LOW LEVEL APIs ---------- // get interface count virtual int __stdcall getInterfaceCount() = 0; // get interface information virtual SCCI_INTERFACE_INFO* __stdcall getInterfaceInfo(int iInterfaceNo) = 0; // get interface instance virtual SoundInterface* __stdcall getInterface(int iInterfaceNo) = 0; // release interface instance virtual BOOL __stdcall releaseInterface(SoundInterface* pSoundInterface) = 0; // release all interface instance virtual BOOL __stdcall releaseAllInterface() = 0; // ---------- HI LEVEL APIs ---------- // get sound chip instance virtual SoundChip* __stdcall getSoundChip(int iSoundChipType,DWORD dClock) = 0; // release sound chip instance virtual BOOL __stdcall releaseSoundChip(SoundChip* pSoundChip) = 0; // release all sound chip instance virtual BOOL __stdcall releaseAllSoundChip() = 0; // set delay time virtual BOOL __stdcall setDelay(DWORD dMSec) = 0; // get delay time virtual DWORD __stdcall getDelay() = 0; // reset interfaces(A sound chips initialize after interface reset) virtual BOOL __stdcall reset() = 0; // initialize sound chips virtual BOOL __stdcall init() = 0; // Sound Interface instance initialize virtual BOOL __stdcall initializeInstance() = 0; // Sound Interface instance release virtual BOOL __stdcall releaseInstance() = 0; // config scci // !!!this function is scciconfig exclusive use!!! virtual BOOL __stdcall config() = 0; // get version info /*virtual DWORD __stdcall getVersion(DWORD *pMVersion = NULL) = 0;*/ virtual DWORD __stdcall getVersion(DWORD *pMVersion = nullptr) = 0; // get Level mater disp valid virtual BOOL __stdcall isValidLevelDisp() = 0; // get Level mater disp visible virtual BOOL __stdcall isLevelDisp() = 0; // set Level mater disp visible virtual void __stdcall setLevelDisp(BOOL bDisp) = 0; // set mode virtual void __stdcall setMode(int iMode) = 0; // send datas virtual void __stdcall sendData() = 0; // clear buffer virtual void __stdcall clearBuff() = 0; // set Acquisition Mode(Sound Chip) virtual void __stdcall setAcquisitionMode(int iMode) = 0; // set Acquisition Mode clock renge virtual void __stdcall setAcquisitionModeClockRenge(DWORD dClock) = 0; // set command buffer size virtual BOOL __stdcall setCommandBuffetSize(DWORD dBuffSize) = 0; // buffer check virtual BOOL __stdcall isBufferEmpty() = 0; }; //---------------------------------------- // Sound Interface(LOW level APIs) //---------------------------------------- class SoundInterface{ public: // support low level API check virtual BOOL __stdcall isSupportLowLevelApi() = 0; // send data to interface virtual BOOL __stdcall setData(BYTE *pData,DWORD dSendDataLen) = 0; // get data from interface virtual DWORD __stdcall getData(BYTE *pData,DWORD dGetDataLen) = 0; // set delay time virtual BOOL __stdcall setDelay(DWORD dDelay) = 0; // get delay time virtual DWORD __stdcall getDelay() = 0; // reset interface virtual BOOL __stdcall reset() = 0; // initialize sound chips virtual BOOL __stdcall init() = 0; // サウンドチップ数取得 virtual DWORD __stdcall getSoundChipCount() = 0; // サウンドチップ取得 virtual SoundChip* __stdcall getSoundChip(DWORD dNum) = 0; }; //---------------------------------------- // Sound Chip //---------------------------------------- class SoundChip{ public: // get sound chip information virtual SCCI_SOUND_CHIP_INFO* __stdcall getSoundChipInfo() = 0; // get sound chip type virtual int __stdcall getSoundChipType() = 0; // set Register data virtual BOOL __stdcall setRegister(DWORD dAddr,DWORD dData) = 0; // get Register data(It may not be supported) virtual DWORD __stdcall getRegister(DWORD dAddr) = 0; // initialize sound chip(clear registers) virtual BOOL __stdcall init() = 0; // get sound chip clock virtual DWORD __stdcall getSoundChipClock() = 0; // get writed register data virtual DWORD __stdcall getWrittenRegisterData(DWORD addr) = 0; // buffer check virtual BOOL __stdcall isBufferEmpty() = 0; }; //---------------------------------------- // get sound interface manager function //---------------------------------------- typedef SoundInterfaceManager* (__stdcall *SCCIFUNC)(void); //---------------------------------------- // pcm callback function // void callback(SCCIPCMDATA *pPcm,DWORD dSize) //---------------------------------------- typedef struct { int iL; int iR; } SCCIPCMDATA; } BambooTracker-0.3.5/BambooTracker/command/000077500000000000000000000000001362177441300203735ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/command/abstract_command.hpp000066400000000000000000000004311362177441300244030ustar00rootroot00000000000000#pragma once #include "command_id.hpp" struct AbstractCommand { virtual ~AbstractCommand() {} virtual void redo() = 0; virtual void undo() = 0; virtual CommandId getID() const = 0; virtual bool mergeWith(const AbstractCommand* other) { (void)other; return false; } }; BambooTracker-0.3.5/BambooTracker/command/command_id.hpp000066400000000000000000000025121362177441300231760ustar00rootroot00000000000000#pragma once enum CommandId : int { // 0x1*: Instrument list AddInstrument = 0x10, RemoveInstrument = 0x11, ChangeInstrumentName = 0x12, CloneInstrument = 0x13, DeepCloneInstrument = 0x14, // 0x2*, 0x3*: Pattern editor SetKeyOnToStep = 0x20, SetKeyOffToStep = 0x21, EraseStep = 0x22, SetInstrumentInStep = 0x23, EraseInstrumentInStep = 0x24, SetVolumeToStep = 0x25, EraseVolumeInStep = 0x26, SetEffectIDToStep = 0x27, EraseEffectInStep = 0x28, SetEffectValueToStep = 0x29, EraseEffectValueInStep = 0x2a, InsertStep = 0x2b, DeletePreviousStep = 0x2c, PasteCopiedDataToPattern = 0x2d, EraseCellsInPattern = 0x2e, PasteMixCopiedDataToPattern = 0x2f, IncreaseNoteKeyInPattern = 0x30, DecreaseNoteKeyInPattern = 0x31, IncreaseNoteOctaveInPattern = 0x32, DecreaseNoteOctaveInPattern = 0x33, ExpandPattern = 0x34, ShrinkPattern = 0x35, SetEchoBufferAccess = 0x36, InterpolatePattern = 0x37, ReversePattern = 0x38, ReplaceInstrumentInPattern = 0x39, PasteOverwriteCopiedDataToPattern = 0x3a, // 0x4*: Order list SetPatternToOrder = 0x40, InsertOrderBelow = 0x41, DeleteOrder = 0x42, PasteCopiedDataToOrder = 0x43, DuplicateOrder = 0x44, MoveOrder = 0x45, ClonePatterns = 0x46, CloneOrder = 0x47 }; BambooTracker-0.3.5/BambooTracker/command/command_manager.cpp000066400000000000000000000014631362177441300242130ustar00rootroot00000000000000#include "command_manager.hpp" #include CommandManager::CommandManager() {} void CommandManager::invoke(CommandIPtr command) { command->redo(); redoStack_ = std::stack(); if (undoStack_.empty() || !undoStack_.top()->mergeWith(command.get())) { undoStack_.push(std::move(command)); } } void CommandManager::undo() { if (undoStack_.empty()) return; CommandIPtr command = std::move(undoStack_.top()); command->undo(); undoStack_.pop(); redoStack_.push(std::move(command)); } void CommandManager::redo() { if (redoStack_.empty()) return; CommandIPtr command = std::move(redoStack_.top()); command->redo(); redoStack_.pop(); undoStack_.push(std::move(command)); } void CommandManager::clear() { redoStack_ = std::stack(); undoStack_ = std::stack(); } BambooTracker-0.3.5/BambooTracker/command/command_manager.hpp000066400000000000000000000005411362177441300242140ustar00rootroot00000000000000#pragma once #include #include #include "abstract_command.hpp" class CommandManager { public: using CommandIPtr = std::unique_ptr; CommandManager(); void invoke(CommandIPtr command); void undo(); void redo(); void clear(); private: std::stack undoStack_; std::stack redoStack_; }; BambooTracker-0.3.5/BambooTracker/command/commands.hpp000066400000000000000000000044031362177441300227060ustar00rootroot00000000000000#pragma once /********** Instrument edit **********/ #include "./instrument/add_instrument_command.hpp" #include "./instrument/remove_instrument_command.hpp" #include "./instrument/change_instrument_name_command.hpp" #include "./instrument/clone_instrument_command.hpp" #include "./instrument/deep_clone_instrument_command.hpp" /********** Pattern edit **********/ #include "./pattern/set_key_on_to_step_command.hpp" #include "./pattern/set_key_off_to_step_command.hpp" #include "./pattern/erase_step_command.hpp" #include "./pattern/set_instrument_to_step_command.hpp" #include "./pattern/erase_instrument_in_step_command.hpp" #include "./pattern/set_volume_to_step_command.hpp" #include "./pattern/erase_volume_in_step_command.hpp" #include "./pattern/set_effect_id_to_step_command.hpp" #include "./pattern/erase_effect_in_step_command.hpp" #include "./pattern/set_effect_value_to_step_command.hpp" #include "./pattern/erase_effect_value_in_step_command.hpp" #include "./pattern/insert_step_command.hpp" #include "./pattern/delete_previous_step_command.hpp" #include "./pattern/paste_copied_data_to_pattern_command.hpp" #include "./pattern/erase_cells_in_pattern_command.hpp" #include "./pattern/paste_mix_copied_data_to_pattern_command.hpp" #include "./pattern/increase_note_key_in_pattern_command.hpp" #include "./pattern/decrease_note_key_in_pattern_command.hpp" #include "./pattern/increase_note_octave_in_pattern_command.hpp" #include "./pattern/decrease_note_octave_in_pattern_command.hpp" #include "./pattern/expand_pattern_command.hpp" #include "./pattern/shrink_pattern_command.hpp" #include "./pattern/set_echo_buffer_access_command.hpp" #include "./pattern/interpolate_pattern_command.hpp" #include "./pattern/reverse_pattern_command.hpp" #include "./pattern/replace_instrument_in_pattern_command.hpp" #include "./pattern/paste_overwrite_copied_data_to_pattern_command.hpp" /********** Order edit **********/ #include "./order/set_pattern_to_order_command.hpp" #include "./order/insert_order_below_command.hpp" #include "./order/delete_order_command.hpp" #include "./order/paste_copied_data_to_order_command.hpp" #include "./order/duplicate_order_command.hpp" #include "./order/move_order_command.hpp" #include "./order/clone_patterns_command.hpp" #include "./order/clone_order_command.hpp" BambooTracker-0.3.5/BambooTracker/command/instrument/000077500000000000000000000000001362177441300226035ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/command/instrument/add_instrument_command.cpp000066400000000000000000000014371362177441300300320ustar00rootroot00000000000000#include "add_instrument_command.hpp" #include AddInstrumentCommand::AddInstrumentCommand(std::weak_ptr manager, int num, SoundSource source, std::string name) : manager_(manager), num_(num), source_(source), name_(name) {} AddInstrumentCommand::AddInstrumentCommand(std::weak_ptr manager, std::unique_ptr inst) : manager_(manager), inst_(std::move(inst)) { num_ = inst_->getNumber(); } void AddInstrumentCommand::redo() { if (inst_) manager_.lock()->addInstrument(inst_->clone()); else manager_.lock()->addInstrument(num_, source_, name_); } void AddInstrumentCommand::undo() { manager_.lock()->removeInstrument(num_); } CommandId AddInstrumentCommand::getID() const { return CommandId::AddInstrument; } BambooTracker-0.3.5/BambooTracker/command/instrument/add_instrument_command.hpp000066400000000000000000000012541362177441300300340ustar00rootroot00000000000000#pragma once #include #include #include "abstract_command.hpp" #include "instrument.hpp" #include "instruments_manager.hpp" #include "misc.hpp" class AddInstrumentCommand : public AbstractCommand { public: AddInstrumentCommand(std::weak_ptr manager, int num, SoundSource source, std::string name); AddInstrumentCommand(std::weak_ptr manager, std::unique_ptr inst); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr manager_; int num_; SoundSource source_; std::string name_; std::unique_ptr inst_; }; BambooTracker-0.3.5/BambooTracker/command/instrument/change_instrument_name_command.cpp000066400000000000000000000011221362177441300315160ustar00rootroot00000000000000#include "change_instrument_name_command.hpp" ChangeInstrumentNameCommand::ChangeInstrumentNameCommand(std::weak_ptr manager, int num, std::string name) : manager_(manager), instNum_(num), newName_(name) { oldName_ = manager_.lock()->getInstrumentName(instNum_); } void ChangeInstrumentNameCommand::redo() { manager_.lock()->setInstrumentName(instNum_, newName_); } void ChangeInstrumentNameCommand::undo() { manager_.lock()->setInstrumentName(instNum_, oldName_); } CommandId ChangeInstrumentNameCommand::getID() const { return CommandId::ChangeInstrumentName; } BambooTracker-0.3.5/BambooTracker/command/instrument/change_instrument_name_command.hpp000066400000000000000000000007331362177441300315320ustar00rootroot00000000000000#pragma once #include #include #include "abstract_command.hpp" #include "instruments_manager.hpp" class ChangeInstrumentNameCommand : public AbstractCommand { public: ChangeInstrumentNameCommand(std::weak_ptr manager, int num, std::string name); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr manager_; int instNum_; std::string oldName_, newName_; }; BambooTracker-0.3.5/BambooTracker/command/instrument/clone_instrument_command.cpp000066400000000000000000000007671362177441300304070ustar00rootroot00000000000000#include "clone_instrument_command.hpp" cloneInstrumentCommand::cloneInstrumentCommand(std::weak_ptr manager, int num, int refNum) : manager_(manager), cloneInstNum_(num), refInstNum_(refNum) { } void cloneInstrumentCommand::redo() { manager_.lock()->cloneInstrument(cloneInstNum_, refInstNum_); } void cloneInstrumentCommand::undo() { manager_.lock()->removeInstrument(cloneInstNum_); } CommandId cloneInstrumentCommand::getID() const { return CommandId::CloneInstrument; } BambooTracker-0.3.5/BambooTracker/command/instrument/clone_instrument_command.hpp000066400000000000000000000006521362177441300304050ustar00rootroot00000000000000#pragma once #include #include "abstract_command.hpp" #include "instruments_manager.hpp" class cloneInstrumentCommand : public AbstractCommand { public: cloneInstrumentCommand(std::weak_ptr manager, int num, int refNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr manager_; int cloneInstNum_, refInstNum_; }; BambooTracker-0.3.5/BambooTracker/command/instrument/deep_clone_instrument_command.cpp000066400000000000000000000010301362177441300313640ustar00rootroot00000000000000#include "deep_clone_instrument_command.hpp" DeepCloneInstrumentCommand::DeepCloneInstrumentCommand(std::weak_ptr manager, int num, int refNum) : manager_(manager), cloneInstNum_(num), refInstNum_(refNum) { } void DeepCloneInstrumentCommand::redo() { manager_.lock()->deepCloneInstrument(cloneInstNum_, refInstNum_); } void DeepCloneInstrumentCommand::undo() { manager_.lock()->removeInstrument(cloneInstNum_); } CommandId DeepCloneInstrumentCommand::getID() const { return CommandId::DeepCloneInstrument; } BambooTracker-0.3.5/BambooTracker/command/instrument/deep_clone_instrument_command.hpp000066400000000000000000000006621362177441300314030ustar00rootroot00000000000000#pragma once #include #include "abstract_command.hpp" #include "instruments_manager.hpp" class DeepCloneInstrumentCommand : public AbstractCommand { public: DeepCloneInstrumentCommand(std::weak_ptr manager, int num, int refNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr manager_; int cloneInstNum_, refInstNum_; }; BambooTracker-0.3.5/BambooTracker/command/instrument/remove_instrument_command.cpp000066400000000000000000000007461362177441300306010ustar00rootroot00000000000000#include "remove_instrument_command.hpp" #include RemoveInstrumentCommand::RemoveInstrumentCommand(std::weak_ptr manager, int number) : manager_(manager), number_(number) {} void RemoveInstrumentCommand::redo() { inst_ = manager_.lock()->removeInstrument(number_); } void RemoveInstrumentCommand::undo() { manager_.lock()->addInstrument(std::move(inst_)); } CommandId RemoveInstrumentCommand::getID() const { return CommandId::RemoveInstrument; } BambooTracker-0.3.5/BambooTracker/command/instrument/remove_instrument_command.hpp000066400000000000000000000007261362177441300306040ustar00rootroot00000000000000#pragma once #include #include "abstract_command.hpp" #include "instruments_manager.hpp" #include "instrument.hpp" class RemoveInstrumentCommand : public AbstractCommand { public: RemoveInstrumentCommand(std::weak_ptr manager, int number); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr manager_; int number_; std::unique_ptr inst_; }; BambooTracker-0.3.5/BambooTracker/command/order/000077500000000000000000000000001362177441300215065ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/command/order/clone_order_command.cpp000066400000000000000000000017261362177441300262110ustar00rootroot00000000000000#include "clone_order_command.hpp" CloneOrderCommand::CloneOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : mod_(mod), song_(songNum), order_(orderNum) { } void CloneOrderCommand::redo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_); for (auto& t : sng.getTrackAttributes()) { auto& track = sng.getTrack(t.number); // Set previous pattern to avoid leaving unused pattern track.registerPatternToOrder(order_ + 1, track.getPatternFromOrderNumber(order_).getNumber()); track.registerPatternToOrder(order_ + 1, track.clonePattern(track.getOrderData(order_).patten)); } } void CloneOrderCommand::undo() { auto& sng = mod_.lock()->getSong(song_); for (auto& t : sng.getTrackAttributes()) { auto& p = sng.getTrack(t.number).getPatternFromOrderNumber(order_ + 1); if (p.getUsedCount() == 1) p.clear(); } sng.deleteOrder(order_ + 1); } CommandId CloneOrderCommand::getID() const { return CommandId::CloneOrder; } BambooTracker-0.3.5/BambooTracker/command/order/clone_order_command.hpp000066400000000000000000000006641362177441300262160ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" #include "track.hpp" class CloneOrderCommand : public AbstractCommand { public: CloneOrderCommand(std::weak_ptr mod, int songNum, int orderNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, order_; std::vector prevOdr_; }; BambooTracker-0.3.5/BambooTracker/command/order/clone_patterns_command.cpp000066400000000000000000000026011362177441300267270ustar00rootroot00000000000000#include "clone_patterns_command.hpp" ClonePatternsCommand::ClonePatternsCommand(std::weak_ptr mod, int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack) : mod_(mod), song_(songNum), bOrder_(beginOrder), bTrack_(beginTrack), eOrder_(endOrder), eTrack_(endTrack) { for (int o = beginOrder; o <= endOrder; ++o) { prevOdrs_.emplace_back(); for (int t = beginTrack; t <= endTrack; ++t) { prevOdrs_.at(static_cast(o - beginOrder)).push_back( mod_.lock()->getSong(songNum).getTrack(t).getOrderData(o)); } } } void ClonePatternsCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int o = bOrder_; o <= eOrder_; ++o) { for (int t = bTrack_; t <= eTrack_; ++t) { auto& track = sng.getTrack(t); track.registerPatternToOrder(o, track.clonePattern(track.getOrderData(o).patten)); } } } void ClonePatternsCommand::undo() { auto& sng = mod_.lock()->getSong(song_); for (int o = bOrder_; o <= eOrder_; ++o) { for (int t = bTrack_; t <= eTrack_; ++t) { auto& track = sng.getTrack(t); auto& p = track.getPatternFromOrderNumber(o); if (p.getUsedCount() == 1) p.clear(); track.registerPatternToOrder( o, prevOdrs_.at(static_cast(o - bOrder_)).at(static_cast(t - bTrack_)).patten); } } } CommandId ClonePatternsCommand::getID() const { return CommandId::ClonePatterns; } BambooTracker-0.3.5/BambooTracker/command/order/clone_patterns_command.hpp000066400000000000000000000010311362177441300267300ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" #include "track.hpp" class ClonePatternsCommand : public AbstractCommand { public: ClonePatternsCommand(std::weak_ptr mod, int songNum, int beginOrder, int beginTrack, int endOrder, int endTrack); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bOrder_, bTrack_, eOrder_, eTrack_; std::vector> prevOdrs_; }; BambooTracker-0.3.5/BambooTracker/command/order/delete_order_command.cpp000066400000000000000000000012171362177441300263460ustar00rootroot00000000000000#include "delete_order_command.hpp" DeleteOrderCommand::DeleteOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : mod_(mod), song_(songNum), order_(orderNum) { prevOdr_ = mod_.lock()->getSong(songNum).getOrderData(orderNum); } void DeleteOrderCommand::redo() { mod_.lock()->getSong(song_).deleteOrder(order_); } void DeleteOrderCommand::undo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_ - 1); for (const auto& t : prevOdr_) { sng.getTrack(t.trackAttribute.number).registerPatternToOrder(t.order, t.patten); } } CommandId DeleteOrderCommand::getID() const { return CommandId::DeleteOrder; } BambooTracker-0.3.5/BambooTracker/command/order/delete_order_command.hpp000066400000000000000000000006661362177441300263620ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" #include "track.hpp" class DeleteOrderCommand : public AbstractCommand { public: DeleteOrderCommand(std::weak_ptr mod, int songNum, int orderNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, order_; std::vector prevOdr_; }; BambooTracker-0.3.5/BambooTracker/command/order/duplicate_order_command.cpp000066400000000000000000000012201362177441300270500ustar00rootroot00000000000000#include "duplicate_order_command.hpp" DuplicateOrderCommand::DuplicateOrderCommand(std::weak_ptr mod, int songNum, int orderNum) : mod_(mod), song_(songNum), order_(orderNum) { } void DuplicateOrderCommand::redo() { auto& sng = mod_.lock()->getSong(song_); sng.insertOrderBelow(order_); for (auto& t : sng.getTrackAttributes()) { auto& track = sng.getTrack(t.number); track.registerPatternToOrder(order_ + 1, track.getOrderData(order_).patten); } } void DuplicateOrderCommand::undo() { mod_.lock()->getSong(song_).deleteOrder(order_ + 1); } CommandId DuplicateOrderCommand::getID() const { return CommandId::DuplicateOrder; } BambooTracker-0.3.5/BambooTracker/command/order/duplicate_order_command.hpp000066400000000000000000000006271362177441300270670ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class DuplicateOrderCommand : public AbstractCommand { public: DuplicateOrderCommand(std::weak_ptr mod, int songNum, int orderNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, order_; }; BambooTracker-0.3.5/BambooTracker/command/order/insert_order_below_command.cpp000066400000000000000000000007471362177441300276070ustar00rootroot00000000000000#include "insert_order_below_command.hpp" InsertOrderBelowCommand::InsertOrderBelowCommand(std::weak_ptr mod, int songNum, int orderNum) : mod_(mod), song_(songNum), order_(orderNum) { } void InsertOrderBelowCommand::redo() { mod_.lock()->getSong(song_).insertOrderBelow(order_); } void InsertOrderBelowCommand::undo() { mod_.lock()->getSong(song_).deleteOrder(order_ + 1); } CommandId InsertOrderBelowCommand::getID() const { return CommandId::InsertOrderBelow; } BambooTracker-0.3.5/BambooTracker/command/order/insert_order_below_command.hpp000066400000000000000000000005671362177441300276140ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class InsertOrderBelowCommand : public AbstractCommand { public: InsertOrderBelowCommand(std::weak_ptr mod, int songNum, int orderNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, order_; }; BambooTracker-0.3.5/BambooTracker/command/order/move_order_command.cpp000066400000000000000000000010321362177441300260450ustar00rootroot00000000000000#include "move_order_command.hpp" MoveOrderCommand::MoveOrderCommand(std::weak_ptr mod, int songNum, int orderNum, bool isUp) : mod_(mod), song_(songNum), order_(orderNum), isUp_(isUp) { } void MoveOrderCommand::redo() { swap(); } void MoveOrderCommand::undo() { swap(); } CommandId MoveOrderCommand::getID() const { return CommandId::MoveOrder; } void MoveOrderCommand::swap() { auto& sng = mod_.lock()->getSong(song_); if (isUp_) sng.swapOrder(order_ - 1, order_); else sng.swapOrder(order_, order_ + 1); } BambooTracker-0.3.5/BambooTracker/command/order/move_order_command.hpp000066400000000000000000000006201362177441300260540ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class MoveOrderCommand : public AbstractCommand { public: MoveOrderCommand(std::weak_ptr mod, int songNum, int orderNum, bool isUp); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, order_; bool isUp_; void swap(); }; BambooTracker-0.3.5/BambooTracker/command/order/paste_copied_data_to_order_command.cpp000066400000000000000000000025021362177441300312340ustar00rootroot00000000000000#include "paste_copied_data_to_order_command.hpp" #include "track.hpp" PasteCopiedDataToOrderCommand::PasteCopiedDataToOrderCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, std::vector> cells) : mod_(mod), song_(songNum), track_(beginTrack), order_(beginOrder), cells_(cells) { auto& sng = mod.lock()->getSong(songNum); for (size_t i = 0; i < cells.size(); ++i) { prevCells_.emplace_back(); std::vector odrs = sng.getOrderData(beginOrder + static_cast(i)); for (size_t j = 0; j < cells.at(i).size(); ++j) { prevCells_.at(i).push_back(std::to_string(odrs.at(static_cast(beginTrack) + j).patten)); } } } void PasteCopiedDataToOrderCommand::redo() { setCells(cells_); } void PasteCopiedDataToOrderCommand::undo() { setCells(prevCells_); } CommandId PasteCopiedDataToOrderCommand::getID() const { return CommandId::PasteCopiedDataToOrder; } void PasteCopiedDataToOrderCommand::setCells(std::vector>& cells) { auto& sng = mod_.lock()->getSong(song_); for (size_t i = 0; i < cells.size(); ++i) { for (size_t j = 0; j < cells.at(i).size(); ++j) { sng.getTrack(track_ + static_cast(j)) .registerPatternToOrder(order_ + static_cast(i), std::stoi(cells.at(i).at(j))); } } } BambooTracker-0.3.5/BambooTracker/command/order/paste_copied_data_to_order_command.hpp000066400000000000000000000011711362177441300312420ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class PasteCopiedDataToOrderCommand : public AbstractCommand { public: PasteCopiedDataToOrderCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, std::vector> cells); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_; std::vector> cells_, prevCells_; void setCells(std::vector>& cells); }; BambooTracker-0.3.5/BambooTracker/command/order/set_pattern_to_order_command.cpp000066400000000000000000000031611362177441300301360ustar00rootroot00000000000000#include "set_pattern_to_order_command.hpp" SetPatternToOrderCommand::SetPatternToOrderCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), pattern_(patternNum), isSecond_(secondEntry) { prevPattern_ = mod_.lock()->getSong(songNum).getTrack(trackNum) .getPatternFromOrderNumber(orderNum).getNumber(); } void SetPatternToOrderCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).registerPatternToOrder(order_, pattern_); } void SetPatternToOrderCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).registerPatternToOrder(order_, prevPattern_); isSecond_ = true; // Forced complete } CommandId SetPatternToOrderCommand::getID() const { return CommandId::SetPatternToOrder; } bool SetPatternToOrderCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecond_) { auto com = dynamic_cast(other); if (com->getSong() == song_ && com->getTrack() == track_ && com->getOrder() == order_ && com->isSecondEntry()) { pattern_ = (pattern_ << 4) + com->getPattern(); redo(); isSecond_ = true; return true; } } isSecond_ = true; return false; } int SetPatternToOrderCommand::getSong() const { return song_; } int SetPatternToOrderCommand::getTrack() const { return track_; } int SetPatternToOrderCommand::getOrder() const { return order_; } bool SetPatternToOrderCommand::isSecondEntry() const { return isSecond_; } int SetPatternToOrderCommand::getPattern() const { return pattern_; } BambooTracker-0.3.5/BambooTracker/command/order/set_pattern_to_order_command.hpp000066400000000000000000000012221362177441300301370ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class SetPatternToOrderCommand : public AbstractCommand { public: SetPatternToOrderCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int patternNum, bool secondEntry); void redo() override; void undo() override; CommandId getID() const override; bool mergeWith(const AbstractCommand* other) override; int getSong() const; int getTrack() const; int getOrder() const; bool isSecondEntry() const; int getPattern() const; private: std::weak_ptr mod_; int song_, track_, order_, pattern_; int prevPattern_; bool isSecond_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/000077500000000000000000000000001362177441300220505ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/command/pattern/decrease_note_key_in_pattern_command.cpp000066400000000000000000000031231362177441300321440ustar00rootroot00000000000000#include "decrease_note_key_in_pattern_command.hpp" DecreaseNoteKeyInPatternCommand::DecreaseNoteKeyInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { int n = sng.getTrack(track).getPatternFromOrderNumber(beginOrder) .getStep(step).getNoteNumber(); if (n > -1) prevKeys_.push_back(n); } } } void DecreaseNoteKeyInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); int n = s.getNoteNumber(); if (n >= 0) { n = (n == 0)? 0 : (n - 1); s.setNoteNumber(n); } } } } void DecreaseNoteKeyInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); if (s.getNoteNumber() > -1) s.setNoteNumber(prevKeys_.at(i++)); } } } CommandId DecreaseNoteKeyInPatternCommand::getID() const { return CommandId::DecreaseNoteKeyInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/decrease_note_key_in_pattern_command.hpp000066400000000000000000000010601362177441300321470ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class DecreaseNoteKeyInPatternCommand : public AbstractCommand { public: DecreaseNoteKeyInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; std::vector prevKeys_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/decrease_note_octave_in_pattern_command.cpp000066400000000000000000000031771362177441300326460ustar00rootroot00000000000000#include "decrease_note_octave_in_pattern_command.hpp" DecreaseNoteOctaveInPatternCommand::DecreaseNoteOctaveInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { int n = sng.getTrack(track).getPatternFromOrderNumber(beginOrder) .getStep(step).getNoteNumber(); if (n > -1) prevKeys_.push_back(n); } } } void DecreaseNoteOctaveInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); int n = s.getNoteNumber(); if (n > -1) { n = s.getNoteNumber() - 12; if (n < 0) n = 0; s.setNoteNumber(n); } } } } void DecreaseNoteOctaveInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); if (s.getNoteNumber() > -1) s.setNoteNumber(prevKeys_.at(i++)); } } } CommandId DecreaseNoteOctaveInPatternCommand::getID() const { return CommandId::DecreaseNoteOctaveInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/decrease_note_octave_in_pattern_command.hpp000066400000000000000000000010661362177441300326460ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class DecreaseNoteOctaveInPatternCommand : public AbstractCommand { public: DecreaseNoteOctaveInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; std::vector prevKeys_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/delete_previous_step_command.cpp000066400000000000000000000024101362177441300305000ustar00rootroot00000000000000#include "delete_previous_step_command.hpp" DeletePreviousStepCommand::DeletePreviousStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { auto& st = mod_.lock()->getSong(songNum).getTrack(trackNum) .getPatternFromOrderNumber(orderNum).getStep(stepNum - 1); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < 4; ++i) { prevEffID_[i] = st.getEffectID(i); prevEffVal_[i] = st.getEffectValue(i); } } void DeletePreviousStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_) .getPatternFromOrderNumber(order_).deletePreviousStep(step_); } void DeletePreviousStepCommand::undo() { auto& pt = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_); pt.insertStep(step_ - 1); // Insert previous step auto& st = pt.getStep(step_ - 1); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < 4; ++i) { st.setEffectID(i, prevEffID_[i]); st.setEffectValue(i, prevEffVal_[i]); } } CommandId DeletePreviousStepCommand::getID() const { return CommandId::DeletePreviousStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/delete_previous_step_command.hpp000066400000000000000000000010101362177441300305000ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class DeletePreviousStepCommand : public AbstractCommand { public: DeletePreviousStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_, prevEffVal_[4]; std::string prevEffID_[4]; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_cells_in_pattern_command.cpp000066400000000000000000000140701362177441300307600ustar00rootroot00000000000000#include "erase_cells_in_pattern_command.hpp" EraseCellsInPatternCommand::EraseCellsInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColmn), order_(beginOrder), bStep_(beginStep) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; size_t h = static_cast(endStep - beginStep + 1); int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColmn + 1); break; } else { w += (cl + 1); cl = 10; --tr; } } size_t ww = static_cast(w); for (size_t i = 0; i < h; ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < ww; ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void EraseCellsInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setNoteNumber(-1); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setInstrumentNumber(-1); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setVolume(-1); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(0, "--"); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(0, -1); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(1, "--"); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(1, -1); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(2, "--"); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(2, -1); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(3, "--"); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(3, -1); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void EraseCellsInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId EraseCellsInPatternCommand::getID() const { return CommandId::EraseCellsInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_cells_in_pattern_command.hpp000066400000000000000000000011331362177441300307610ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class EraseCellsInPatternCommand : public AbstractCommand { public: EraseCellsInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; std::vector> prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_effect_in_step_command.cpp000066400000000000000000000017421362177441300304120ustar00rootroot00000000000000#include "erase_effect_in_step_command.hpp" EraseEffectInStepCommand::EraseEffectInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n) { auto& st = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum).getStep(stepNum); prevEffID_ = st.getEffectID(n); prevEffVal_ = st.getEffectValue(n); } void EraseEffectInStepCommand::redo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setEffectID(n_, "--"); st.setEffectValue(n_, -1); } void EraseEffectInStepCommand::undo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setEffectID(n_, prevEffID_); st.setEffectValue(n_, prevEffVal_); } CommandId EraseEffectInStepCommand::getID() const { return CommandId::EraseEffectInStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_effect_in_step_command.hpp000066400000000000000000000007531362177441300304200ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class EraseEffectInStepCommand : public AbstractCommand { public: EraseEffectInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; std::string prevEffID_; int prevEffVal_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_effect_value_in_step_command.cpp000066400000000000000000000016021362177441300316010ustar00rootroot00000000000000#include "erase_effect_value_in_step_command.hpp" EraseEffectValueInStepCommand::EraseEffectValueInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n) { prevVal_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectValue(n); } void EraseEffectValueInStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setEffectValue(n_, -1); } void EraseEffectValueInStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setEffectValue(n_, prevVal_); } CommandId EraseEffectValueInStepCommand::getID() const { return CommandId::EraseEffectValueInStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_effect_value_in_step_command.hpp000066400000000000000000000007071362177441300316130ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class EraseEffectValueInStepCommand : public AbstractCommand { public: EraseEffectValueInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; int prevVal_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_instrument_in_step_command.cpp000066400000000000000000000015631362177441300313670ustar00rootroot00000000000000#include "erase_instrument_in_step_command.hpp" EraseInstrumentInStepCommand::EraseInstrumentInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { prevInst_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getInstrumentNumber(); } void EraseInstrumentInStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setInstrumentNumber(-1); } void EraseInstrumentInStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setInstrumentNumber(prevInst_); } CommandId EraseInstrumentInStepCommand::getID() const { return CommandId::EraseInstrumentInStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_instrument_in_step_command.hpp000066400000000000000000000006731362177441300313750ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class EraseInstrumentInStepCommand : public AbstractCommand { public: EraseInstrumentInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevInst_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_step_command.cpp000066400000000000000000000024201362177441300264020ustar00rootroot00000000000000#include "erase_step_command.hpp" EraseStepCommand::EraseStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { auto& st = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum).getStep(stepNum); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < 4; ++i) { prevEffID_[i] = st.getEffectID(i); prevEffVal_[i] = st.getEffectValue(i); } } void EraseStepCommand::redo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(-1); st.setInstrumentNumber(-1); st.setVolume(-1); for (int i = 0; i < 4; ++i){ st.setEffectID(i, "--"); st.setEffectValue(i, -1); } } void EraseStepCommand::undo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < 4; ++i) { st.setEffectID(i, prevEffID_[i]); st.setEffectValue(i, prevEffVal_[i]); } } CommandId EraseStepCommand::getID() const { return CommandId::EraseStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_step_command.hpp000066400000000000000000000007661362177441300264220ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class EraseStepCommand : public AbstractCommand { public: EraseStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_, prevEffVal_[4]; std::string prevEffID_[4]; }; BambooTracker-0.3.5/BambooTracker/command/pattern/erase_volume_in_step_command.cpp000066400000000000000000000014671362177441300304710ustar00rootroot00000000000000#include "erase_volume_in_step_command.hpp" EraseVolumeInStepCommand::EraseVolumeInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { prevVol_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getVolume(); } void EraseVolumeInStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setVolume(-1); } void EraseVolumeInStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setVolume(prevVol_); } CommandId EraseVolumeInStepCommand::getID() const { return CommandId::EraseVolumeInStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/erase_volume_in_step_command.hpp000066400000000000000000000006621362177441300304720ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class EraseVolumeInStepCommand : public AbstractCommand { public: EraseVolumeInStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevVol_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/expand_pattern_command.cpp000066400000000000000000000154701362177441300272750ustar00rootroot00000000000000#include "expand_pattern_command.hpp" ExpandPatternCommand::ExpandPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColmn), order_(beginOrder), bStep_(beginStep) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; size_t h = static_cast(endStep - beginStep + 1); int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColmn + 1); break; } else { w += (cl + 1); cl = 10; --tr; } } size_t ww = static_cast(w); for (size_t i = 0; i < h; ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < ww; ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void ExpandPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: { int n = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setNoteNumber(n); break; } case 1: { int n = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setInstrumentNumber(n); break; } case 2: { int v = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setVolume(v); break; } case 3: { std::string id = (i % 2) ? "--" : prevCells_.at(i / 2).at(j); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(0, id); break; } case 4: { int v = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(0, v); break; } case 5: { std::string id = (i % 2) ? "--" : prevCells_.at(i / 2).at(j); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(1, id); break; } case 6: { int v = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(1, v); break; } case 7: { std::string id = (i % 2) ? "--" : prevCells_.at(i / 2).at(j); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(2, id); break; } case 8: { int v = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(2, v); break; } case 9: { std::string id = (i % 2) ? "--" : prevCells_.at(i / 2).at(j); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(3, id); break; } case 10: { int v = (i % 2) ? -1 : std::stoi(prevCells_.at(i / 2).at(j)); sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(3, v); break; } } ++c; t += (c / 11); c %= 11; } ++s; } } void ExpandPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId ExpandPatternCommand::getID() const { return CommandId::ExpandPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/expand_pattern_command.hpp000066400000000000000000000011111362177441300272650ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class ExpandPatternCommand : public AbstractCommand { public: ExpandPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; std::vector> prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/increase_note_key_in_pattern_command.cpp000066400000000000000000000031251362177441300321640ustar00rootroot00000000000000#include "increase_note_key_in_pattern_command.hpp" IncreaseNoteKeyInPatternCommand::IncreaseNoteKeyInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { int n = sng.getTrack(track).getPatternFromOrderNumber(beginOrder) .getStep(step).getNoteNumber(); if (n > -1) prevKeys_.push_back(n); } } } void IncreaseNoteKeyInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); int n = s.getNoteNumber(); if (n > -1) { n = (n == 95)? 95 : (n + 1); s.setNoteNumber(n); } } } } void IncreaseNoteKeyInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); if (s.getNoteNumber() > -1) s.setNoteNumber(prevKeys_.at(i++)); } } } CommandId IncreaseNoteKeyInPatternCommand::getID() const { return CommandId::IncreaseNoteKeyInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/increase_note_key_in_pattern_command.hpp000066400000000000000000000010601362177441300321650ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class IncreaseNoteKeyInPatternCommand : public AbstractCommand { public: IncreaseNoteKeyInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; std::vector prevKeys_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/increase_note_octave_in_pattern_command.cpp000066400000000000000000000032011362177441300326500ustar00rootroot00000000000000#include "increase_note_octave_in_pattern_command.hpp" IncreaseNoteOctaveInPatternCommand::IncreaseNoteOctaveInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { int n = sng.getTrack(track).getPatternFromOrderNumber(beginOrder) .getStep(step).getNoteNumber(); if (n > -1) prevKeys_.push_back(n); } } } void IncreaseNoteOctaveInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); int n = s.getNoteNumber(); if (n > -1) { n = s.getNoteNumber() + 12; if (n > 95) n = 95; s.setNoteNumber(n); } } } } void IncreaseNoteOctaveInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); if (s.getNoteNumber() > -1) s.setNoteNumber(prevKeys_.at(i++)); } } } CommandId IncreaseNoteOctaveInPatternCommand::getID() const { return CommandId::IncreaseNoteOctaveInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/increase_note_octave_in_pattern_command.hpp000066400000000000000000000010661362177441300326640ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class IncreaseNoteOctaveInPatternCommand : public AbstractCommand { public: IncreaseNoteOctaveInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; std::vector prevKeys_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/insert_step_command.cpp000066400000000000000000000011441362177441300266110ustar00rootroot00000000000000#include "insert_step_command.hpp" InsertStepCommand::InsertStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { } void InsertStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).insertStep(step_); } void InsertStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).deletePreviousStep(step_ + 1); } CommandId InsertStepCommand::getID() const { return CommandId::InsertStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/insert_step_command.hpp000066400000000000000000000006251362177441300266210ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class InsertStepCommand : public AbstractCommand { public: InsertStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/interpolate_pattern_command.cpp000066400000000000000000000207731362177441300303460ustar00rootroot00000000000000#include "interpolate_pattern_command.hpp" InterpolatePatternCommand::InterpolatePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColmn), order_(beginOrder), bStep_(beginStep), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; size_t h = static_cast(endStep - beginStep + 1); int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColmn + 1); break; } else { w += (cl + 1); cl = 10; --tr; } } size_t ww = static_cast(w); for (size_t i = 0; i < h; ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < ww; ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void InterpolatePatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int div = static_cast(prevCells_.size()) - 1; if (!div) div = 1; int t = bTrack_; int c = bCol_; for (size_t i = 0; i < prevCells_.front().size(); ++i) { int s = bStep_; for (size_t j = 0; j < prevCells_.size(); ++j) { switch (c) { case 0: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getNoteNumber(); int b = pattern.getStep(eStep_).getNoteNumber(); if (a > -1 && b > -1) pattern.getStep(s).setNoteNumber(a + (b - a) * static_cast(j) / div); break; } case 1: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getInstrumentNumber(); int b = pattern.getStep(eStep_).getInstrumentNumber(); if (a > -1 && b > -1) pattern.getStep(s).setInstrumentNumber(a + (b - a) * static_cast(j) / div); break; } case 2: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getVolume(); int b = pattern.getStep(eStep_).getVolume(); if (a > -1 && b > -1) pattern.getStep(s).setVolume(a + (b - a) * static_cast(j) / div); break; } case 3: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); std::string a = pattern.getStep(bStep_).getEffectID(0); std::string b = pattern.getStep(eStep_).getEffectID(0); if (a == b) sng.getTrack(t).getPatternFromOrderNumber(order_) .getStep(s).setEffectID(0, a); break; } case 4: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getEffectValue(0); int b = pattern.getStep(eStep_).getEffectValue(0); if (a > -1 && b > -1) pattern.getStep(s).setEffectValue(0, a + (b - a) * static_cast(j) / div); break; } case 5: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); std::string a = pattern.getStep(bStep_).getEffectID(1); std::string b = pattern.getStep(eStep_).getEffectID(1); if (a == b) sng.getTrack(t).getPatternFromOrderNumber(order_) .getStep(s).setEffectID(1, a); break; } case 6: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getEffectValue(1); int b = pattern.getStep(eStep_).getEffectValue(1); if (a > -1 && b > -1) pattern.getStep(s).setEffectValue(1, a + (b - a) * static_cast(j) / div); break; } case 7: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); std::string a = pattern.getStep(bStep_).getEffectID(2); std::string b = pattern.getStep(eStep_).getEffectID(2); if (a == b) sng.getTrack(t).getPatternFromOrderNumber(order_) .getStep(s).setEffectID(2, a); break; } case 8: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getEffectValue(2); int b = pattern.getStep(eStep_).getEffectValue(2); if (a > -1 && b > -1) pattern.getStep(s).setEffectValue(2, a + (b - a) * static_cast(j) / div); break; } case 9: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); std::string a = pattern.getStep(bStep_).getEffectID(3); std::string b = pattern.getStep(eStep_).getEffectID(3); if (a == b) sng.getTrack(t).getPatternFromOrderNumber(order_) .getStep(s).setEffectID(3, a); break; } case 10: { auto& pattern = sng.getTrack(t).getPatternFromOrderNumber(order_); int a = pattern.getStep(bStep_).getEffectValue(3); int b = pattern.getStep(eStep_).getEffectValue(3); if (a > -1 && b > -1) pattern.getStep(s).setEffectValue(3, a + (b - a) * static_cast(j) / div); break; } } ++s; } ++c; t += (c / 11); c %= 11; } } void InterpolatePatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId InterpolatePatternCommand::getID() const { return CommandId::InterpolatePattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/interpolate_pattern_command.hpp000066400000000000000000000011441362177441300303420ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class InterpolatePatternCommand : public AbstractCommand { public: InterpolatePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; int eStep_; std::vector> prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/paste_copied_data_to_pattern_command.cpp000066400000000000000000000107341362177441300321460ustar00rootroot00000000000000#include "paste_copied_data_to_pattern_command.hpp" PasteCopiedDataToPatternCommand::PasteCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) : mod_(mod), song_(songNum), track_(beginTrack), col_(beginColmn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; for (size_t i = 0; i < cells.size(); ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < cells.at(i).size(); ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void PasteCopiedDataToPatternCommand::redo() { setCells(cells_); } void PasteCopiedDataToPatternCommand::undo() { setCells(prevCells_); } CommandId PasteCopiedDataToPatternCommand::getID() const { return CommandId::PasteCopiedDataToPattern; } void PasteCopiedDataToPatternCommand::setCells(std::vector>& cells) { auto& sng = mod_.lock()->getSong(song_); int s = step_; for (size_t i = 0; i < cells.size(); ++i) { int t = track_; int c = col_; for (size_t j = 0; j < cells.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(cells.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(cells.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(cells.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, cells.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(cells.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, cells.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(cells.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, cells.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(cells.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, cells.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(cells.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } BambooTracker-0.3.5/BambooTracker/command/pattern/paste_copied_data_to_pattern_command.hpp000066400000000000000000000012411362177441300321440ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class PasteCopiedDataToPatternCommand : public AbstractCommand { public: PasteCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; std::vector> cells_, prevCells_; void setCells(std::vector>& cells); }; BambooTracker-0.3.5/BambooTracker/command/pattern/paste_mix_copied_data_to_pattern_command.cpp000066400000000000000000000146141362177441300330240ustar00rootroot00000000000000#include "paste_mix_copied_data_to_pattern_command.hpp" PasteMixCopiedDataToPatternCommand::PasteMixCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) : mod_(mod), song_(songNum), track_(beginTrack), col_(beginColmn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; for (size_t i = 0; i < cells.size(); ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < cells.at(i).size(); ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void PasteMixCopiedDataToPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int s = step_; for (size_t i = 0; i < cells_.size(); ++i) { int t = track_; int c = col_; for (size_t j = 0; j < cells_.at(i).size(); ++j) { auto& step = sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s); switch (c) { case 0: { int n = std::stoi(cells_.at(i).at(j)); if (n != -1 && step.getNoteNumber() == -1) step.setNoteNumber(n); break; } case 1: { int n = std::stoi(cells_.at(i).at(j)); if (n != -1 && step.getInstrumentNumber() == -1) step.setInstrumentNumber(n); break; } case 2: { int vol = std::stoi(cells_.at(i).at(j)); if (vol != -1 && step.getVolume() == -1) step.setVolume(vol); break; } case 3: { std::string id = cells_.at(i).at(j); if (id != "--" && step.getEffectID(0) == "--") step.setEffectID(0, id); break; } case 4: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1 && step.getEffectValue(0) == -1) step.setEffectValue(0, val); break; } case 5: { std::string id = cells_.at(i).at(j); if (id != "--" && step.getEffectID(1) == "--") step.setEffectID(1, id); break; } case 6: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1 && step.getEffectValue(1) == -1) step.setEffectValue(1, val); break; } case 7: { std::string id = cells_.at(i).at(j); if (id != "--" && step.getEffectID(2) == "--") step.setEffectID(2, id); break; } case 8: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1 && step.getEffectValue(2) == -1) step.setEffectValue(2, val); break; } case 9: { std::string id = cells_.at(i).at(j); if (id != "--" && step.getEffectID(3) == "--") step.setEffectID(3, id); break; } case 10: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1 && step.getEffectValue(3) == -1) step.setEffectValue(3, val); break; } } ++c; t += (c / 11); c %= 11; } ++s; } } void PasteMixCopiedDataToPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = step_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = track_; int c = col_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId PasteMixCopiedDataToPatternCommand::getID() const { return CommandId::PasteMixCopiedDataToPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/paste_mix_copied_data_to_pattern_command.hpp000066400000000000000000000011531362177441300330230ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class PasteMixCopiedDataToPatternCommand : public AbstractCommand { public: PasteMixCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; std::vector> cells_, prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/paste_overwrite_copied_data_to_pattern_command.cpp000066400000000000000000000153461362177441300342600ustar00rootroot00000000000000#include "paste_overwrite_copied_data_to_pattern_command.hpp" PasteOverwriteCopiedDataToPatternCommand::PasteOverwriteCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells) : mod_(mod), song_(songNum), track_(beginTrack), col_(beginColmn), order_(beginOrder), step_(beginStep), cells_(cells) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; for (size_t i = 0; i < cells.size(); ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < cells.at(i).size(); ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void PasteOverwriteCopiedDataToPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int s = step_; for (size_t i = 0; i < cells_.size(); ++i) { int t = track_; int c = col_; for (size_t j = 0; j < cells_.at(i).size(); ++j) { switch (c) { case 0: { int n = std::stoi(cells_.at(i).at(j)); if (n != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setNoteNumber(n); break; } case 1: { int n = std::stoi(cells_.at(i).at(j)); if (n != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setInstrumentNumber(n); break; } case 2: { int vol = std::stoi(cells_.at(i).at(j)); if (vol != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setVolume(vol); break; } case 3: { std::string id = cells_.at(i).at(j); if (id != "--") sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(0, id); break; } case 4: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(0, val); break; } case 5: { std::string id = cells_.at(i).at(j); if (id != "--") sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(1, id); break; } case 6: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(1, val); break; } case 7: { std::string id = cells_.at(i).at(j); if (id != "--") sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(2, id); break; } case 8: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(2, val); break; } case 9: { std::string id = cells_.at(i).at(j); if (id != "--") sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(3, id); break; } case 10: { int val = std::stoi(cells_.at(i).at(j)); if (val != -1) sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(3, val); break; } } ++c; t += (c / 11); c %= 11; } ++s; } } void PasteOverwriteCopiedDataToPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = step_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = track_; int c = col_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId PasteOverwriteCopiedDataToPatternCommand::getID() const { return CommandId::PasteOverwriteCopiedDataToPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/paste_overwrite_copied_data_to_pattern_command.hpp000066400000000000000000000011671362177441300342610ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class PasteOverwriteCopiedDataToPatternCommand : public AbstractCommand { public: PasteOverwriteCopiedDataToPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, std::vector> cells); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, col_, order_, step_; std::vector> cells_, prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/replace_instrument_in_pattern_command.cpp000066400000000000000000000031731362177441300324040ustar00rootroot00000000000000#include "replace_instrument_in_pattern_command.hpp" ReplaceInstrumentInPatternCommand::ReplaceInstrumentInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInst) : mod_(mod), song_(songNum), bTrack_(beginTrack), order_(beginOrder), bStep_(beginStep), eTrack_(endTrack), eStep_(endStep), inst_(newInst) { auto& sng = mod.lock()->getSong(songNum); for (int step = beginStep; step <= endStep; ++step) { for (int track = beginTrack; track <= endTrack; ++track) { int n = sng.getTrack(track).getPatternFromOrderNumber(beginOrder) .getStep(step).getInstrumentNumber(); if (n > -1) prevInsts_.push_back(n); } } } void ReplaceInstrumentInPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); int n = s.getInstrumentNumber(); if (n > -1) s.setInstrumentNumber(inst_); } } } void ReplaceInstrumentInPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); size_t i = 0; for (int step = bStep_; step <= eStep_; ++step) { for (int track = bTrack_; track <= eTrack_; ++track) { auto& s = sng.getTrack(track).getPatternFromOrderNumber(order_).getStep(step); if (s.getInstrumentNumber() > -1) s.setInstrumentNumber(prevInsts_.at(i)); } } } CommandId ReplaceInstrumentInPatternCommand::getID() const { return CommandId::ReplaceInstrumentInPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/replace_instrument_in_pattern_command.hpp000066400000000000000000000011461362177441300324070ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class ReplaceInstrumentInPatternCommand : public AbstractCommand { public: ReplaceInstrumentInPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginOrder, int beginStep, int endTrack, int endStep, int newInst); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_; int bTrack_, order_, bStep_; int eTrack_, eStep_; int inst_; std::vector prevInsts_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/reverse_pattern_command.cpp000066400000000000000000000147651362177441300274770ustar00rootroot00000000000000#include "reverse_pattern_command.hpp" ReversePatternCommand::ReversePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColmn), order_(beginOrder), bStep_(beginStep) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; size_t h = static_cast(endStep - beginStep + 1); int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColmn + 1); break; } else { w += (cl + 1); cl = 10; --tr; } } size_t ww = static_cast(w); for (size_t i = 0; i < h; ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < ww; ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void ReversePatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); size_t l = prevCells_.size() - 1; int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(l - i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(l - i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(l - i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(l - i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(l - i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(l - i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(l - i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(l - i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(l - i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(l - i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(l - i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void ReversePatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId ReversePatternCommand::getID() const { return CommandId::ReversePattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/reverse_pattern_command.hpp000066400000000000000000000011241362177441300274650ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class ReversePatternCommand : public AbstractCommand { public: ReversePatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; std::vector> prevCells_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_echo_buffer_access_command.cpp000066400000000000000000000015631362177441300307220ustar00rootroot00000000000000#include "set_echo_buffer_access_command.hpp" SetEchoBufferAccessCommand::SetEchoBufferAccessCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int bufNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), buf_(bufNum) { prevNote_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getNoteNumber(); } void SetEchoBufferAccessCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setNoteNumber(-buf_ - 3); } void SetEchoBufferAccessCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setNoteNumber(prevNote_); } CommandId SetEchoBufferAccessCommand::getID() const { return CommandId::SetEchoBufferAccess; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_echo_buffer_access_command.hpp000066400000000000000000000007331362177441300307250ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class SetEchoBufferAccessCommand : public AbstractCommand { public: SetEchoBufferAccessCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int bufNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_, buf_; int prevNote_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_effect_id_to_step_command.cpp000066400000000000000000000044751362177441300306040ustar00rootroot00000000000000#include "set_effect_id_to_step_command.hpp" SetEffectIDToStepCommand::SetEffectIDToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n), effID_(id), isSecond_(secondEntry) { Step& step = mod_.lock()->getSong(songNum).getTrack(trackNum) .getPatternFromOrderNumber(orderNum).getStep(stepNum); prevEffID_ = step.getEffectID(n); filledValue00_ = fillValue00 && (step.getEffectValue(n) == -1); } void SetEffectIDToStepCommand::redo() { std::string str = isSecond_ ? effID_ : ("0" + effID_); Step& step = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); step.setEffectID(n_, str); if (filledValue00_) step.setEffectValue(n_, 0); } void SetEffectIDToStepCommand::undo() { Step& step = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); step.setEffectID(n_, prevEffID_); if (filledValue00_) step.setEffectValue(n_, -1); if (!isSecond_) { // Forced complete effID_ = "0" + effID_; isSecond_ = true; } } CommandId SetEffectIDToStepCommand::getID() const { return CommandId::SetEffectIDToStep; } bool SetEffectIDToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecond_) { auto com = dynamic_cast(other); if (com->getSong() == song_ && com->getTrack() == track_ && com->getOrder() == order_ && com->getStep() == step_ && com->getN() == n_ && com->isSecondEntry()) { effID_ = effID_ + com->getEffectID(); isSecond_ = true; redo(); return true; } } // Enterd only 1 character if (!isSecond_) { effID_ = "0" + effID_; isSecond_ = true; } return false; } int SetEffectIDToStepCommand::getSong() const { return song_; } int SetEffectIDToStepCommand::getTrack() const { return track_; } int SetEffectIDToStepCommand::getOrder() const { return order_; } int SetEffectIDToStepCommand::getStep() const { return step_; } int SetEffectIDToStepCommand::getN() const { return n_; } bool SetEffectIDToStepCommand::isSecondEntry() const { return isSecond_; } std::string SetEffectIDToStepCommand::getEffectID() const { return effID_; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_effect_id_to_step_command.hpp000066400000000000000000000014511362177441300306000ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class SetEffectIDToStepCommand : public AbstractCommand { public: SetEffectIDToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, std::string id, bool fillValue00, bool secondEntry); void redo() override; void undo() override; CommandId getID() const override; bool mergeWith(const AbstractCommand* other) override; int getSong() const; int getTrack() const; int getOrder() const; int getStep() const; int getN() const; bool isSecondEntry() const; std::string getEffectID() const; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; std::string effID_, prevEffID_; bool filledValue00_; bool isSecond_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_effect_value_to_step_command.cpp000066400000000000000000000045131362177441300313150ustar00rootroot00000000000000#include "set_effect_value_to_step_command.hpp" SetEffectValueToStepCommand::SetEffectValueToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), n_(n), val_(value), ctrl_(ctrl), isSecond_(secondEntry) { prevVal_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getEffectValue(n); } void SetEffectValueToStepCommand::redo() { int value; switch (ctrl_) { case EffectDisplayControl::Unset: value = val_; break; case EffectDisplayControl::ReverseFMVolumeDelay: value = (val_ < 0x80) ? (0x7f - val_) : val_; break; case EffectDisplayControl::ReverseFMBrightness: value = (val_ > 0) ? (0xff - val_ + 1) : val_; break; } mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setEffectValue(n_, value); } void SetEffectValueToStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setEffectValue(n_, prevVal_); isSecond_ = true; // Forced complete } CommandId SetEffectValueToStepCommand::getID() const { return CommandId::SetEffectValueToStep; } bool SetEffectValueToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecond_) { auto com = dynamic_cast(other); if (com->getSong() == song_ && com->getTrack() == track_ && com->getOrder() == order_ && com->getStep() == step_ && com->getN() == n_ && com->isSecondEntry()) { val_ = (val_ << 4) + com->getEffectValue(); redo(); isSecond_ = true; return true; } } isSecond_ = true; return false; } int SetEffectValueToStepCommand::getSong() const { return song_; } int SetEffectValueToStepCommand::getTrack() const { return track_; } int SetEffectValueToStepCommand::getOrder() const { return order_; } int SetEffectValueToStepCommand::getStep() const { return step_; } int SetEffectValueToStepCommand::getN() const { return n_; } bool SetEffectValueToStepCommand::isSecondEntry() const { return isSecond_; } int SetEffectValueToStepCommand::getEffectValue() const { return val_; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_effect_value_to_step_command.hpp000066400000000000000000000014631362177441300313230ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" #include "misc.hpp" class SetEffectValueToStepCommand : public AbstractCommand { public: SetEffectValueToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int n, int value, EffectDisplayControl ctrl, bool secondEntry); void redo() override; void undo() override; CommandId getID() const override; bool mergeWith(const AbstractCommand* other) override; int getSong() const; int getTrack() const; int getOrder() const; int getStep() const; int getN() const; bool isSecondEntry() const; int getEffectValue() const; private: std::weak_ptr mod_; int song_, track_, order_, step_, n_; int val_, prevVal_; EffectDisplayControl ctrl_; bool isSecond_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_instrument_to_step_command.cpp000066400000000000000000000035371362177441300311020ustar00rootroot00000000000000#include "set_instrument_to_step_command.hpp" SetInstrumentToStepCommand::SetInstrumentToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), inst_(instNum), isSecond_(secondEntry) { prevInst_ = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum) .getStep(stepNum).getInstrumentNumber(); } void SetInstrumentToStepCommand::redo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setInstrumentNumber(inst_); } void SetInstrumentToStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setInstrumentNumber(prevInst_); isSecond_ = true; // Forced complete } CommandId SetInstrumentToStepCommand::getID() const { return CommandId::SetInstrumentInStep; } bool SetInstrumentToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecond_) { auto com = dynamic_cast(other); if (com->getSong() == song_ && com->getTrack() == track_ && com->getOrder() == order_ && com->getStep() == step_ && com->isSecondEntry()) { inst_ = (inst_ << 4) + com->getInst(); redo(); isSecond_ = true; return true; } } isSecond_ = true; return false; } int SetInstrumentToStepCommand::getSong() const { return song_; } int SetInstrumentToStepCommand::getTrack() const { return track_; } int SetInstrumentToStepCommand::getOrder() const { return order_; } int SetInstrumentToStepCommand::getStep() const { return step_; } bool SetInstrumentToStepCommand::isSecondEntry() const { return isSecond_; } int SetInstrumentToStepCommand::getInst() const { return inst_; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_instrument_to_step_command.hpp000066400000000000000000000012641362177441300311020ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class SetInstrumentToStepCommand : public AbstractCommand { public: SetInstrumentToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int instNum, bool secondEntry); void redo() override; void undo() override; CommandId getID() const override; bool mergeWith(const AbstractCommand* other) override; int getSong() const; int getTrack() const; int getOrder() const; int getStep() const; bool isSecondEntry() const; int getInst() const; private: std::weak_ptr mod_; int song_, track_, order_, step_, inst_; int prevInst_; bool isSecond_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_key_off_to_step_command.cpp000066400000000000000000000024761362177441300303150ustar00rootroot00000000000000#include "set_key_off_to_step_command.hpp" SetKeyOffToStepCommand::SetKeyOffToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum) { auto& st = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum).getStep(stepNum); prevNote_ = st.getNoteNumber(); prevInst_ = st.getInstrumentNumber(); prevVol_ = st.getVolume(); for (int i = 0; i < 4; ++i) { prevEffID_[i] = st.getEffectID(i); prevEffVal_[i] = st.getEffectValue(i); } } void SetKeyOffToStepCommand::redo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(-2); st.setInstrumentNumber(-1); st.setVolume(-1); for (int i = 0; i < 4; ++i) { st.setEffectID(i, "--"); st.setEffectValue(i, -1); } } void SetKeyOffToStepCommand::undo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(prevNote_); st.setInstrumentNumber(prevInst_); st.setVolume(prevVol_); for (int i = 0; i < 4; ++i) { st.setEffectID(i, prevEffID_[i]); st.setEffectValue(i, prevEffVal_[i]); } } CommandId SetKeyOffToStepCommand::getID() const { return CommandId::SetKeyOffToStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_key_off_to_step_command.hpp000066400000000000000000000010021362177441300303020ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include "module.hpp" class SetKeyOffToStepCommand : public AbstractCommand { public: SetKeyOffToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_; int prevNote_, prevInst_, prevVol_, prevEffVal_[4]; std::string prevEffID_[4]; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_key_on_to_step_command.cpp000066400000000000000000000021571362177441300301530ustar00rootroot00000000000000#include "set_key_on_to_step_command.hpp" SetKeyOnToStepCommand::SetKeyOnToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int noteNum, bool autosetInst, int instNum) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), note_(noteNum), inst_(instNum), autosetInst_(autosetInst) { auto& st = mod_.lock()->getSong(songNum).getTrack(trackNum).getPatternFromOrderNumber(orderNum).getStep(stepNum); prevNote_ = st.getNoteNumber(); if (autosetInst) prevInst_ = st.getInstrumentNumber(); } void SetKeyOnToStepCommand::redo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(note_); if (autosetInst_) st.setInstrumentNumber(inst_); } void SetKeyOnToStepCommand::undo() { auto& st = mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_).getStep(step_); st.setNoteNumber(prevNote_); if (autosetInst_) st.setInstrumentNumber(prevInst_); } CommandId SetKeyOnToStepCommand::getID() const { return CommandId::SetKeyOnToStep; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_key_on_to_step_command.hpp000066400000000000000000000010161362177441300301510ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class SetKeyOnToStepCommand : public AbstractCommand { public: SetKeyOnToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int noteNum, bool autosetInst, int instNum); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, track_, order_, step_, note_, inst_; int prevNote_, prevInst_; bool autosetInst_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/set_volume_to_step_command.cpp000066400000000000000000000036061362177441300301760ustar00rootroot00000000000000#include "set_volume_to_step_command.hpp" #include "misc.hpp" SetVolumeToStepCommand::SetVolumeToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry) : mod_(mod), song_(songNum), track_(trackNum), order_(orderNum), step_(stepNum), vol_(volume), isFMReserved_(isFMReversed), isSecond_(secondEntry) { prevVol_ = mod_.lock()->getSong(songNum).getTrack(trackNum) .getPatternFromOrderNumber(orderNum).getStep(stepNum).getVolume(); } void SetVolumeToStepCommand::redo() { int volume = (isFMReserved_ && vol_ < 0x80) ? (0x7f - vol_) : vol_; mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setVolume(volume); } void SetVolumeToStepCommand::undo() { mod_.lock()->getSong(song_).getTrack(track_).getPatternFromOrderNumber(order_) .getStep(step_).setVolume(prevVol_); isSecond_ = true; // Forced complete } CommandId SetVolumeToStepCommand::getID() const { return CommandId::SetVolumeToStep; } bool SetVolumeToStepCommand::mergeWith(const AbstractCommand* other) { if (other->getID() == getID() && !isSecond_) { auto com = dynamic_cast(other); if (com->getSong() == song_ && com->getTrack() == track_ && com->getOrder() == order_ && com->getStep() == step_ && com->isSecondEntry()) { vol_ = (vol_ << 4) + com->getVol(); redo(); isSecond_ = true; return true; } } isSecond_ = true; return false; } int SetVolumeToStepCommand::getSong() const { return song_; } int SetVolumeToStepCommand::getTrack() const { return track_; } int SetVolumeToStepCommand::getOrder() const { return order_; } int SetVolumeToStepCommand::getStep() const { return step_; } bool SetVolumeToStepCommand::isSecondEntry() const { return isSecond_; } int SetVolumeToStepCommand::getVol() const { return vol_; } BambooTracker-0.3.5/BambooTracker/command/pattern/set_volume_to_step_command.hpp000066400000000000000000000013411362177441300301750ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include "module.hpp" class SetVolumeToStepCommand : public AbstractCommand { public: SetVolumeToStepCommand(std::weak_ptr mod, int songNum, int trackNum, int orderNum, int stepNum, int volume, bool isFMReversed, bool secondEntry); void redo() override; void undo() override; CommandId getID() const override; bool mergeWith(const AbstractCommand* other) override; int getSong() const; int getTrack() const; int getOrder() const; int getStep() const; bool isSecondEntry() const; int getVol() const; private: std::weak_ptr mod_; int song_, track_, order_, step_, vol_; int prevVol_; bool isFMReserved_; bool isSecond_; }; BambooTracker-0.3.5/BambooTracker/command/pattern/shrink_pattern_command.cpp000066400000000000000000000173621362177441300273160ustar00rootroot00000000000000#include "shrink_pattern_command.hpp" ShrinkPatternCommand::ShrinkPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep) : mod_(mod), song_(songNum), bTrack_(beginTrack), bCol_(beginColmn), order_(beginOrder), bStep_(beginStep), eStep_(endStep) { auto& sng = mod.lock()->getSong(songNum); int s = beginStep; size_t h = static_cast(endStep - beginStep + 1); int w = 0; int tr = endTrack; int cl = endColumn; while (true) { if (tr == beginTrack) { w += (cl - beginColmn + 1); break; } else { w += (cl + 1); cl = 10; --tr; } } size_t ww = static_cast(w); for (size_t i = 0; i < h; ++i) { prevCells_.emplace_back(); int t = beginTrack; int c = beginColmn; for (size_t j = 0; j < ww; ++j) { switch (c) { case 0: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getNoteNumber())); break; case 1: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getInstrumentNumber())); break; case 2: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getVolume())); break; case 3: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(0)); break; case 4: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(0))); break; case 5: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(1)); break; case 6: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(1))); break; case 7: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(2)); break; case 8: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(2))); break; case 9: prevCells_.at(i).push_back( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectID(3)); break; case 10: prevCells_.at(i).push_back(std::to_string( sng.getTrack(t).getPatternFromOrderNumber(beginOrder).getStep(s).getEffectValue(3))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } void ShrinkPatternCommand::redo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); i += 2) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } for (; s <= eStep_; ++s) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(0).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setNoteNumber(-1); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setInstrumentNumber(-1); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setVolume(-1); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(0, "--"); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(0, -1); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(1, "--"); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(1, -1); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(2, "--"); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(2, -1); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectID(3, "--"); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s).setEffectValue(3, -1); break; } ++c; t += (c / 11); c %= 11; } } } void ShrinkPatternCommand::undo() { auto& sng = mod_.lock()->getSong(song_); int s = bStep_; for (size_t i = 0; i < prevCells_.size(); ++i) { int t = bTrack_; int c = bCol_; for (size_t j = 0; j < prevCells_.at(i).size(); ++j) { switch (c) { case 0: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setNoteNumber(std::stoi(prevCells_.at(i).at(j))); break; case 1: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setInstrumentNumber(std::stoi(prevCells_.at(i).at(j))); break; case 2: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setVolume(std::stoi(prevCells_.at(i).at(j))); break; case 3: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(0, prevCells_.at(i).at(j)); break; case 4: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(0, std::stoi(prevCells_.at(i).at(j))); break; case 5: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(1, prevCells_.at(i).at(j)); break; case 6: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(1, std::stoi(prevCells_.at(i).at(j))); break; case 7: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(2, prevCells_.at(i).at(j)); break; case 8: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(2, std::stoi(prevCells_.at(i).at(j))); break; case 9: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectID(3, prevCells_.at(i).at(j)); break; case 10: sng.getTrack(t).getPatternFromOrderNumber(order_).getStep(s) .setEffectValue(3, std::stoi(prevCells_.at(i).at(j))); break; } ++c; t += (c / 11); c %= 11; } ++s; } } CommandId ShrinkPatternCommand::getID() const { return CommandId::ShrinkPattern; } BambooTracker-0.3.5/BambooTracker/command/pattern/shrink_pattern_command.hpp000066400000000000000000000011261362177441300273120ustar00rootroot00000000000000#pragma once #include "abstract_command.hpp" #include #include #include #include "module.hpp" class ShrinkPatternCommand : public AbstractCommand { public: ShrinkPatternCommand(std::weak_ptr mod, int songNum, int beginTrack, int beginColmn, int beginOrder, int beginStep, int endTrack, int endColumn, int endStep); void redo() override; void undo() override; CommandId getID() const override; private: std::weak_ptr mod_; int song_, bTrack_, bCol_, order_, bStep_; int eStep_; std::vector> prevCells_; }; BambooTracker-0.3.5/BambooTracker/configuration.cpp000066400000000000000000000623511362177441300223370ustar00rootroot00000000000000#include "configuration.hpp" #include "jam_manager.hpp" Configuration::Configuration() { // Internal // mainW_ = 930; mainH_= 780; mainMax_ = false; mainX_ = -1; // Dummy mainY_ = -1; // Dummy mainVSplit_ = -1; // Dummy instFMW_ = 570; instFMH_ = 750; instSSGW_ = 500; instSSGH_ = 390; followMode_ = true; workDir_ = ""; instOpenFormat_ = 0; bankOpenFormat_ = 0; // General // // General settings warpCursor_ = true; warpAcrossOrders_ = true; showRowNumHex_ = true; showPrevNextOrders_ = true; backupModules_ = true; dontSelectOnDoubleClick_ = false; reverseFMVolumeOrder_ = true; moveCursorToRight_ = false; retrieveChannelState_ = false; enableTranslation_ = true; showFMDetuneSigned_ = false; showWaveVisual_ = true; fill00ToEffectValue_ = true; autosetInstrument_ = true; moveCursorHScroll_ = true; // Edit settings pageJumpLength_ = 4; editableStep_ = 1; keyRepetision_ = true; // Keys keyOffKey_ = u8"-"; octUpKey_ = u8"Num+*"; octDownKey_ = u8"Num+/"; echoKey_ = u8"^"; noteEntryLayout_ = QWERTY; // Sound // sndAPI_ = u8""; sndDevice_ = u8""; realChip_ = RealChipInterface::NONE; emulator_ = 1; sampleRate_ = 44100; bufferLength_ = 40; // Midi // midiInPort_ = u8""; // Mixer // mixerVolumeMaster_ = 100; mixerVolumeFM_ = 0; mixerVolumeSSG_ = 0; // Input // fmEnvelopeTexts_ = { { "PMD", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip }) }, { "FMP", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB }) }, { "FMP7", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB }) }, { "VOPM", std::vector({ // Number FMEnvelopeTextType::Skip, // LFO FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, // CH FMEnvelopeTextType::Skip, FMEnvelopeTextType::FB, FMEnvelopeTextType::AL, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, // Op FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "NRTDRV", // For VOICE_MODE=0 std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "MXDRV", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::Skip }) }, { "MMLDRV", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::AL, FMEnvelopeTextType::FB, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4, FMEnvelopeTextType::Skip, FMEnvelopeTextType::Skip }) }, { "MUCOM88", std::vector({ FMEnvelopeTextType::Skip, FMEnvelopeTextType::FB, FMEnvelopeTextType::AL, FMEnvelopeTextType::AR1, FMEnvelopeTextType::DR1, FMEnvelopeTextType::SR1, FMEnvelopeTextType::RR1, FMEnvelopeTextType::SL1, FMEnvelopeTextType::TL1, FMEnvelopeTextType::KS1, FMEnvelopeTextType::ML1, FMEnvelopeTextType::DT1, FMEnvelopeTextType::AR2, FMEnvelopeTextType::DR2, FMEnvelopeTextType::SR2, FMEnvelopeTextType::RR2, FMEnvelopeTextType::SL2, FMEnvelopeTextType::TL2, FMEnvelopeTextType::KS2, FMEnvelopeTextType::ML2, FMEnvelopeTextType::DT2, FMEnvelopeTextType::AR3, FMEnvelopeTextType::DR3, FMEnvelopeTextType::SR3, FMEnvelopeTextType::RR3, FMEnvelopeTextType::SL3, FMEnvelopeTextType::TL3, FMEnvelopeTextType::KS3, FMEnvelopeTextType::ML3, FMEnvelopeTextType::DT3, FMEnvelopeTextType::AR4, FMEnvelopeTextType::DR4, FMEnvelopeTextType::SR4, FMEnvelopeTextType::RR4, FMEnvelopeTextType::SL4, FMEnvelopeTextType::TL4, FMEnvelopeTextType::KS4, FMEnvelopeTextType::ML4, FMEnvelopeTextType::DT4 }) } }; // Layouts const std::unordered_map mappingQWERTY = { {u8"Z", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8"M", JamKey::LowB}, {u8",", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"Q", JamKey::HighC}, {u8"2", JamKey::HighCS}, {u8"W", JamKey::HighD}, {u8"3", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"5", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"6", JamKey::HighGS}, {u8"Y", JamKey::HighA}, {u8"7", JamKey::HighAS}, {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"9", JamKey::HighCS2}, {u8"O", JamKey::HighD2}, }; const std::unordered_map mappingQWERTZ = { {u8"Y", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8"M", JamKey::LowB}, {u8",", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"Q", JamKey::HighC}, {u8"2", JamKey::HighCS}, {u8"W", JamKey::HighD}, {u8"3", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"5", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"6", JamKey::HighGS}, {u8"Z", JamKey::HighA}, {u8"7", JamKey::HighAS}, {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"9", JamKey::HighCS2}, {u8"O", JamKey::HighD2}, }; const std::unordered_map mappingAZERTY = { {u8"W", JamKey::LowC}, {u8"S", JamKey::LowCS}, {u8"X", JamKey::LowD}, {u8"D", JamKey::LowDS}, {u8"C", JamKey::LowE}, {u8"V", JamKey::LowF}, {u8"G", JamKey::LowFS}, {u8"B", JamKey::LowG}, {u8"H", JamKey::LowGS}, {u8"N", JamKey::LowA}, {u8"J", JamKey::LowAS}, {u8",", JamKey::LowB}, {u8";", JamKey::LowC2}, {u8"L", JamKey::LowCS2}, {u8".", JamKey::LowD2}, {u8"A", JamKey::HighC}, {u8"É", JamKey::HighCS}, //é - \xc9 {u8"Z", JamKey::HighD}, {u8"\"", JamKey::HighDS}, {u8"E", JamKey::HighE}, {u8"R", JamKey::HighF}, {u8"(", JamKey::HighFS}, {u8"T", JamKey::HighG}, {u8"-", JamKey::HighGS}, {u8"Y", JamKey::HighA}, {u8"È", JamKey::HighAS}, //è - \xc8 {u8"U", JamKey::HighB}, {u8"I", JamKey::HighC2}, {u8"Ç", JamKey::HighCS2}, //ç - \xc7 {u8"O", JamKey::HighD2}, }; mappingCustom = {}; mappingLayouts = { { Custom, mappingCustom }, { QWERTY, mappingQWERTY }, { QWERTZ, mappingQWERTZ }, { AZERTY, mappingAZERTY } }; // Appearance ptnHdFont_ = u8""; ptnHdFontSize_ = 10; ptnRowFont_ = u8""; ptnRowFontSize_ = 10; odrHdFont_ = u8""; odrHdFontSize_ = 10; odrRowFont_ = u8""; odrRowFontSize_ = 10; } // Internal // void Configuration::setMainWindowWidth(int w) { mainW_ = w; } int Configuration::getMainWindowWidth() const { return mainW_; } void Configuration::setMainWindowHeight(int h) { mainH_ = h; } int Configuration::getMainWindowHeight() const { return mainH_; } void Configuration::setMainWindowMaximized(bool isMax) { mainMax_ = isMax; } bool Configuration::getMainWindowMaximized() const { return mainMax_; } void Configuration::setMainWindowX(int x) { mainX_ = x; } int Configuration::getMainWindowX() const { return mainX_; } void Configuration::setMainWindowY(int y) { mainY_ = y; } int Configuration::getMainWindowY() const { return mainY_; } void Configuration::setMainWindowVerticalSplit(int y) { mainVSplit_ = y; } int Configuration::getMainWindowVerticalSplit() const { return mainVSplit_; } void Configuration::setInstrumentFMWindowWidth(int w) { instFMW_ = w; } int Configuration::getInstrumentFMWindowWidth() const { return instFMW_; } void Configuration::setInstrumentFMWindowHeight(int h) { instFMH_ = h; } int Configuration::getInstrumentFMWindowHeight() const { return instFMH_; } void Configuration::setInstrumentSSGWindowWidth(int w) { instSSGW_ = w; } int Configuration::getInstrumentSSGWindowWidth() const { return instSSGW_; } void Configuration::setInstrumentSSGWindowHeight(int h) { instSSGH_ = h; } int Configuration::getInstrumentSSGWindowHeight() const { return instSSGH_; } void Configuration::setFollowMode(bool enabled) { followMode_ = enabled; } bool Configuration::getFollowMode() const { return followMode_; } void Configuration::setWorkingDirectory(std::string path) { workDir_ = path; } std::string Configuration::getWorkingDirectory() const { return workDir_; } void Configuration::setInstrumentOpenFormat(int i) { instOpenFormat_ = i; } int Configuration::getInstrumentOpenFormat() const { return instOpenFormat_; } void Configuration::setBankOpenFormat(int i) { bankOpenFormat_ = i; } int Configuration::getBankOpenFormat() const { return bankOpenFormat_; } // General // // General settings void Configuration::setWarpCursor(bool enabled) { warpCursor_ = enabled; } bool Configuration::getWarpCursor() const { return warpCursor_; } void Configuration::setWarpAcrossOrders(bool enabled) { warpAcrossOrders_ = enabled; } bool Configuration::getWarpAcrossOrders() const { return warpAcrossOrders_; } void Configuration::setShowRowNumberInHex(bool enabled) { showRowNumHex_ = enabled; } bool Configuration::getShowRowNumberInHex() const { return showRowNumHex_; } void Configuration::setShowPreviousNextOrders(bool enabled) { showPrevNextOrders_ = enabled; } bool Configuration::getShowPreviousNextOrders() const { return showPrevNextOrders_; } void Configuration::setBackupModules(bool enabled) { backupModules_ = enabled; } bool Configuration::getBackupModules() const { return backupModules_; } void Configuration::setDontSelectOnDoubleClick(bool enabled) { dontSelectOnDoubleClick_ = enabled; } bool Configuration::getDontSelectOnDoubleClick() const { return dontSelectOnDoubleClick_; } void Configuration::setReverseFMVolumeOrder(bool enabled) { reverseFMVolumeOrder_= enabled; } bool Configuration::getReverseFMVolumeOrder() const { return reverseFMVolumeOrder_; } void Configuration::setMoveCursorToRight(bool enabled) { moveCursorToRight_ = enabled; } bool Configuration::getMoveCursorToRight() const { return moveCursorToRight_; } void Configuration::setRetrieveChannelState(bool enabled) { retrieveChannelState_ = enabled; } bool Configuration::getRetrieveChannelState() const { return retrieveChannelState_; } void Configuration::setEnableTranslation(bool enabled) { enableTranslation_ = enabled; } bool Configuration::getEnableTranslation() const { return enableTranslation_; } void Configuration::setShowFMDetuneAsSigned(bool enabled) { showFMDetuneSigned_ = enabled; } bool Configuration::getShowFMDetuneAsSigned() const { return showFMDetuneSigned_; } void Configuration::setShowWaveVisual(bool enabled) { showWaveVisual_ = enabled; } bool Configuration::getShowWaveVisual() const { return showWaveVisual_; } void Configuration::setFill00ToEffectValue(bool enabled) { fill00ToEffectValue_ = enabled; } bool Configuration::getFill00ToEffectValue() const { return fill00ToEffectValue_; } void Configuration::setAutosetInstrument(bool enabled) { autosetInstrument_ = enabled; } bool Configuration::getAutosetInstrument() const { return autosetInstrument_; } void Configuration::setMoveCursorByHorizontalScroll(bool enabled) { moveCursorHScroll_ = enabled; } bool Configuration::getMoveCursorByHorizontalScroll() const { return moveCursorHScroll_; } // Edit settings void Configuration::setPageJumpLength(size_t length) { pageJumpLength_ = length; } size_t Configuration::getPageJumpLength() const { return pageJumpLength_; } void Configuration::setEditableStep(size_t step) { editableStep_ = step; } size_t Configuration::getEditableStep() const { return editableStep_; } void Configuration::setKeyRepetition(bool enabled) { keyRepetision_ = enabled; } bool Configuration::getKeyRepetition() const { return keyRepetision_; } // Keys void Configuration::setKeyOffKey(std::string key) { keyOffKey_ = key; } std::string Configuration::getKeyOffKey() const { return keyOffKey_; } void Configuration::setOctaveUpKey(std::string key) { octUpKey_ = key; } std::string Configuration::getOctaveUpKey() const { return octUpKey_; } void Configuration::setOctaveDownKey(std::string key) { octDownKey_ = key; } std::string Configuration::getOctaveDownKey() const { return octDownKey_; } void Configuration::setEchoBufferKey(std::string key) { echoKey_ = key; } std::string Configuration::getEchoBufferKey() const { return echoKey_; } void Configuration::setNoteEntryLayout(KeyboardLayout layout) { noteEntryLayout_ = layout; } Configuration::KeyboardLayout Configuration::getNoteEntryLayout() const { return noteEntryLayout_; } void Configuration::setCustomLayoutKeys(std::unordered_map mapping) { mappingLayouts[KeyboardLayout::Custom] = mapping; } std::unordered_map Configuration::getCustomLayoutKeys() const { return mappingLayouts.at(KeyboardLayout::Custom); } // Sound // void Configuration::setSoundAPI(std::string api) { sndAPI_ = api; } std::string Configuration::getSoundAPI() const { return sndAPI_; } void Configuration::setSoundDevice(std::string device) { sndDevice_ = device; } std::string Configuration::getSoundDevice() const { return sndDevice_; } void Configuration::setRealChipInterface(RealChipInterface type) { realChip_ = type; } RealChipInterface Configuration::getRealChipInterface() const { return realChip_; } void Configuration::setEmulator(int emulator) { emulator_ = emulator; } int Configuration::getEmulator() const { return emulator_; } void Configuration::setSampleRate(uint32_t rate) { sampleRate_ = rate; } uint32_t Configuration::getSampleRate() const { return sampleRate_; } void Configuration::setBufferLength(size_t length) { bufferLength_ = length; } size_t Configuration::getBufferLength() const { return bufferLength_; } // Midi // void Configuration::setMidiInputPort(const std::string &port) { midiInPort_ = port; } std::string Configuration::getMidiInputPort() const { return midiInPort_; } // Mixer // void Configuration::setMixerVolumeMaster(int percentage) { mixerVolumeMaster_ = percentage; } int Configuration::getMixerVolumeMaster() const { return mixerVolumeMaster_; } void Configuration::setMixerVolumeFM(double dB) { mixerVolumeFM_ = dB; } double Configuration::getMixerVolumeFM() const { return mixerVolumeFM_; } void Configuration::setMixerVolumeSSG(double dB) { mixerVolumeSSG_ = dB; } double Configuration::getMixerVolumeSSG() const { return mixerVolumeSSG_; } // Input // void Configuration::setFMEnvelopeTexts(std::vector texts) { fmEnvelopeTexts_ = texts; } std::vector Configuration::getFMEnvelopeTexts() const { return fmEnvelopeTexts_; } // Appearrance void Configuration::setPatternEditorHeaderFont(std::string font) { ptnHdFont_ = font; } std::string Configuration::getPatternEditorHeaderFont() const { return ptnHdFont_; } void Configuration::setPatternEditorHeaderFontSize(int size) { ptnHdFontSize_ = size; } int Configuration::getPatternEditorHeaderFontSize() const { return ptnHdFontSize_; } void Configuration::setPatternEditorRowsFont(std::string font) { ptnRowFont_ = font; } std::string Configuration::getPatternEditorRowsFont() const { return ptnRowFont_; } void Configuration::setPatternEditorRowsFontSize(int size) { ptnRowFontSize_ = size; } int Configuration::getPatternEditorRowsFontSize() const { return ptnRowFontSize_; } void Configuration::setOrderListHeaderFont(std::string font) { odrHdFont_ = font; } std::string Configuration::getOrderListHeaderFont() const { return odrHdFont_; } void Configuration::setOrderListHeaderFontSize(int size) { odrHdFontSize_ = size; } int Configuration::getOrderListHeaderFontSize() const { return odrHdFontSize_; } void Configuration::setOrderListRowsFont(std::string font) { odrRowFont_ = font; } std::string Configuration::getOrderListRowsFont() const { return odrRowFont_; } void Configuration::setOrderListRowsFontSize(int size) { odrRowFontSize_ = size; } int Configuration::getOrderListRowsFontSize() const { return odrRowFontSize_; } BambooTracker-0.3.5/BambooTracker/configuration.hpp000066400000000000000000000152321362177441300223400ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "enum_hash.hpp" #include "misc.hpp" enum class JamKey : int; struct FMEnvelopeText { std::string name; std::vector texts; }; class Configuration { public: Configuration(); // Internal // public: void setMainWindowWidth(int w); int getMainWindowWidth() const; void setMainWindowHeight(int h); int getMainWindowHeight() const; void setMainWindowMaximized(bool isMax); bool getMainWindowMaximized() const; void setMainWindowX(int x); int getMainWindowX() const; void setMainWindowY(int y); int getMainWindowY() const; void setMainWindowVerticalSplit(int y); int getMainWindowVerticalSplit() const; void setInstrumentFMWindowWidth(int w); int getInstrumentFMWindowWidth() const; void setInstrumentFMWindowHeight(int h); int getInstrumentFMWindowHeight() const; void setInstrumentSSGWindowWidth(int w); int getInstrumentSSGWindowWidth() const; void setInstrumentSSGWindowHeight(int h); int getInstrumentSSGWindowHeight() const; void setFollowMode(bool enabled); bool getFollowMode() const; void setWorkingDirectory(std::string path); std::string getWorkingDirectory() const; void setInstrumentOpenFormat(int i); int getInstrumentOpenFormat() const; void setBankOpenFormat(int i); int getBankOpenFormat() const; private: int mainW_, mainH_; bool mainMax_; int mainX_, mainY_; int mainVSplit_; int instFMW_, instFMH_; int instSSGW_, instSSGH_; bool followMode_; std::string workDir_; int instOpenFormat_, bankOpenFormat_; // General // // General settings public: void setWarpCursor(bool enabled); bool getWarpCursor() const; void setWarpAcrossOrders(bool enabled); bool getWarpAcrossOrders() const; void setShowRowNumberInHex(bool enabled); bool getShowRowNumberInHex() const; void setShowPreviousNextOrders(bool enabled); bool getShowPreviousNextOrders() const; void setBackupModules(bool enabled); bool getBackupModules() const; void setDontSelectOnDoubleClick(bool enabled); bool getDontSelectOnDoubleClick() const; void setReverseFMVolumeOrder(bool enabled); bool getReverseFMVolumeOrder() const; void setMoveCursorToRight(bool enabled); bool getMoveCursorToRight() const; void setRetrieveChannelState(bool enabled); bool getRetrieveChannelState() const; void setEnableTranslation(bool enabled); bool getEnableTranslation() const; void setShowFMDetuneAsSigned(bool enabled); bool getShowFMDetuneAsSigned() const; void setShowWaveVisual(bool enabled); bool getShowWaveVisual() const; void setFill00ToEffectValue(bool enabled); bool getFill00ToEffectValue() const; void setAutosetInstrument(bool enabled); bool getAutosetInstrument() const; void setMoveCursorByHorizontalScroll(bool enabled); bool getMoveCursorByHorizontalScroll() const; private: bool warpCursor_, warpAcrossOrders_; bool showRowNumHex_, showPrevNextOrders_; bool backupModules_, dontSelectOnDoubleClick_; bool reverseFMVolumeOrder_, moveCursorToRight_; bool retrieveChannelState_, enableTranslation_; bool showFMDetuneSigned_, showWaveVisual_; bool fill00ToEffectValue_, autosetInstrument_; bool moveCursorHScroll_; // Edit settings public: void setPageJumpLength(size_t length); size_t getPageJumpLength() const; void setEditableStep(size_t step); size_t getEditableStep() const; void setKeyRepetition(bool enabled); bool getKeyRepetition() const; private: size_t pageJumpLength_, editableStep_; bool keyRepetision_; // Keys public: void setKeyOffKey(std::string key); std::string getKeyOffKey() const; void setOctaveUpKey(std::string key); std::string getOctaveUpKey() const; void setOctaveDownKey(std::string key); std::string getOctaveDownKey() const; void setEchoBufferKey(std::string key); std::string getEchoBufferKey() const; enum KeyboardLayout : int { // at the top, so new layouts can easily be added in after it // and it's always easy to find no matter how many layouts we add Custom = 0, QWERTY, QWERTZ, AZERTY }; static const std::unordered_map mappingQWERTY, mappingQWERTZ, mappingAZERTY; std::unordered_map mappingCustom; std::unordered_map> mappingLayouts; void setNoteEntryLayout(KeyboardLayout layout); KeyboardLayout getNoteEntryLayout() const; void setCustomLayoutKeys(std::unordered_map mapping); std::unordered_map getCustomLayoutKeys() const; private: std::string keyOffKey_, octUpKey_, octDownKey_, echoKey_; KeyboardLayout noteEntryLayout_; // Sound // public: void setSoundAPI(std::string api); std::string getSoundAPI() const; void setSoundDevice(std::string device); std::string getSoundDevice() const; void setRealChipInterface(RealChipInterface type); RealChipInterface getRealChipInterface() const; void setEmulator(int emulator); int getEmulator() const; void setSampleRate(uint32_t rate); uint32_t getSampleRate() const; void setBufferLength(size_t length); size_t getBufferLength() const; private: std::string sndAPI_, sndDevice_; RealChipInterface realChip_; int emulator_; uint32_t sampleRate_; size_t bufferLength_; // Midi // public: void setMidiInputPort(const std::string &port); std::string getMidiInputPort() const; private: std::string midiInPort_; // Mixer // public: void setMixerVolumeMaster(int percentage); int getMixerVolumeMaster() const; void setMixerVolumeFM(double dB); double getMixerVolumeFM() const; void setMixerVolumeSSG(double dB); double getMixerVolumeSSG() const; private: int mixerVolumeMaster_; double mixerVolumeFM_, mixerVolumeSSG_; // Input // public: void setFMEnvelopeTexts(std::vector texts); std::vector getFMEnvelopeTexts() const; // Appearance // public: void setPatternEditorHeaderFont(std::string font); std::string getPatternEditorHeaderFont() const; void setPatternEditorHeaderFontSize(int size); int getPatternEditorHeaderFontSize() const; void setPatternEditorRowsFont(std::string font); std::string getPatternEditorRowsFont() const; void setPatternEditorRowsFontSize(int size); int getPatternEditorRowsFontSize() const; void setOrderListHeaderFont(std::string font); std::string getOrderListHeaderFont() const; void setOrderListHeaderFontSize(int size); int getOrderListHeaderFontSize() const; void setOrderListRowsFont(std::string font); std::string getOrderListRowsFont() const; void setOrderListRowsFontSize(int size); int getOrderListRowsFontSize() const; private: std::string ptnHdFont_, ptnRowFont_, odrHdFont_, odrRowFont_; int ptnHdFontSize_, ptnRowFontSize_, odrHdFontSize_, odrRowFontSize_; private: std::vector fmEnvelopeTexts_; }; BambooTracker-0.3.5/BambooTracker/enum_hash.hpp000066400000000000000000000010071362177441300214330ustar00rootroot00000000000000#pragma once #if (defined __GNUC__) && (!defined __clang__) #if (__GNUC__ < 6) || ((__GNUC__ == 6) && (__GNUC_MINOR__ < 1)) // Unsupport std::hash with enum types before gcc 6.1. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970 #include namespace std { template struct hash { static_assert(is_enum::value, "..."); size_t operator()(T x) const noexcept { using type = typename underlying_type::type; return hash{}(static_cast(x)); } }; } #endif #endif BambooTracker-0.3.5/BambooTracker/format/000077500000000000000000000000001362177441300202455ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/format/wopn_file.c000066400000000000000000000430711362177441300224000ustar00rootroot00000000000000/* * Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup * * Copyright (c) 2018 Vitaly Novichkov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "wopn_file.h" #include #include static const char *wopn2_magic1 = "WOPN2-BANK\0"; static const char *wopn2_magic2 = "WOPN2-B2NK\0"; static const char *opni_magic1 = "WOPN2-INST\0"; static const char *opni_magic2 = "WOPN2-IN2T\0"; static const uint16_t wopn_latest_version = 2; enum { WOPN_INST_SIZE_V1 = 65, WOPN_INST_SIZE_V2 = 69 }; static uint16_t toUint16LE(const uint8_t *arr) { uint16_t num = arr[0]; num |= ((arr[1] << 8) & 0xFF00); return num; } static uint16_t toUint16BE(const uint8_t *arr) { uint16_t num = arr[1]; num |= ((arr[0] << 8) & 0xFF00); return num; } static int16_t toSint16BE(const uint8_t *arr) { int16_t num = *(const int8_t *)(&arr[0]); num *= 1 << 8; num |= arr[1]; return num; } static void fromUint16LE(uint16_t in, uint8_t *arr) { arr[0] = in & 0x00FF; arr[1] = (in >> 8) & 0x00FF; } static void fromUint16BE(uint16_t in, uint8_t *arr) { arr[1] = in & 0x00FF; arr[0] = (in >> 8) & 0x00FF; } static void fromSint16BE(int16_t in, uint8_t *arr) { arr[1] = in & 0x00FF; arr[0] = ((uint16_t)in >> 8) & 0x00FF; } WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks) { WOPNFile *file = (WOPNFile*)calloc(1, sizeof(WOPNFile)); if(!file) return NULL; file->banks_count_melodic = (melodic_banks != 0) ? melodic_banks : 1; file->banks_melodic = (WOPNBank*)calloc(file->banks_count_melodic, sizeof(WOPNBank)); if(melodic_banks == 0) { unsigned i; for(i = 0; i < 128; ++i) file->banks_melodic[0].ins[i].inst_flags = WOPN_Ins_IsBlank; } file->banks_count_percussion = (percussive_banks != 0) ? percussive_banks : 1; file->banks_percussive = (WOPNBank*)calloc(file->banks_count_percussion, sizeof(WOPNBank)); if(percussive_banks == 0) { unsigned i; for(i = 0; i < 128; ++i) file->banks_percussive[0].ins[i].inst_flags = WOPN_Ins_IsBlank; } return file; } void WOPN_Free(WOPNFile *file) { if(file) { if(file->banks_melodic) free(file->banks_melodic); if(file->banks_percussive) free(file->banks_percussive); free(file); } } int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2) { int res = 1; res &= (bank1->version == bank2->version); res &= (bank1->lfo_freq == bank2->lfo_freq); res &= (bank1->chip_type == bank2->chip_type); res &= (bank1->volume_model == bank2->volume_model); res &= (bank1->banks_count_melodic == bank2->banks_count_melodic); res &= (bank1->banks_count_percussion == bank2->banks_count_percussion); if(res) { int i; for(i = 0; i < bank1->banks_count_melodic; i++) res &= (memcmp(&bank1->banks_melodic[i], &bank2->banks_melodic[i], sizeof(WOPNBank)) == 0); if(res) { for(i = 0; i < bank1->banks_count_percussion; i++) res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPNBank)) == 0); } } return res; } static void WOPN_parseInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) { int l; strncpy(ins->inst_name, (const char*)cursor, 32); ins->inst_name[32] = '\0'; ins->note_offset = toSint16BE(cursor + 32); ins->midi_velocity_offset = 0; /* TODO: for future version > 2 */ ins->percussion_key_number = cursor[34]; ins->inst_flags = 0; /* TODO: for future version > 2 */ ins->fbalg = cursor[35]; ins->lfosens = cursor[36]; for(l = 0; l < 4; l++) { size_t off = 37 + (size_t)(l) * 7; ins->operators[l].dtfm_30 = cursor[off + 0]; ins->operators[l].level_40 = cursor[off + 1]; ins->operators[l].rsatk_50 = cursor[off + 2]; ins->operators[l].amdecay1_60 = cursor[off + 3]; ins->operators[l].decay2_70 = cursor[off + 4]; ins->operators[l].susrel_80 = cursor[off + 5]; ins->operators[l].ssgeg_90 = cursor[off + 6]; } if((version >= 2) && has_sounding_delays) { ins->delay_on_ms = toUint16BE(cursor + 65); ins->delay_off_ms = toUint16BE(cursor + 67); /* Null delays indicate the blank instrument in version 2 */ if((version < 3) && ins->delay_on_ms == 0 && ins->delay_off_ms == 0) ins->inst_flags |= WOPN_Ins_IsBlank; } } static void WOPN_writeInstrument(WOPNInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) { int l; strncpy((char*)cursor, ins->inst_name, 32); fromSint16BE(ins->note_offset, cursor + 32); cursor[34] = ins->percussion_key_number; cursor[35] = ins->fbalg; cursor[36] = ins->lfosens; for(l = 0; l < 4; l++) { size_t off = 37 + (size_t)(l) * 7; cursor[off + 0] = ins->operators[l].dtfm_30; cursor[off + 1] = ins->operators[l].level_40; cursor[off + 2] = ins->operators[l].rsatk_50; cursor[off + 3] = ins->operators[l].amdecay1_60; cursor[off + 4] = ins->operators[l].decay2_70; cursor[off + 5] = ins->operators[l].susrel_80; cursor[off + 6] = ins->operators[l].ssgeg_90; } if((version >= 2) && has_sounding_delays) { if((version < 3) && (ins->inst_flags & WOPN_Ins_IsBlank) != 0) { /* Null delays indicate the blank instrument in version 2 */ fromUint16BE(0, cursor + 65); fromUint16BE(0, cursor + 67); } else { fromUint16BE(ins->delay_on_ms, cursor + 65); fromUint16BE(ins->delay_off_ms, cursor + 67); } } } WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error) { WOPNFile *outFile = NULL; uint16_t i = 0, j = 0, k = 0; uint16_t version = 0; uint16_t count_melodic_banks = 1; uint16_t count_percussive_banks = 1; uint8_t *cursor = (uint8_t *)mem; WOPNBank *bankslots[2]; uint16_t bankslots_sizes[2]; #define SET_ERROR(err) \ {\ WOPN_Free(outFile);\ if(error)\ {\ *error = err;\ }\ } #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } if(!cursor) { SET_ERROR(WOPN_ERR_NULL_POINTER); return NULL; } {/* Magic number */ if(length < 11) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } if(memcmp(cursor, wopn2_magic1, 11) == 0) { version = 1; } else if(memcmp(cursor, wopn2_magic2, 11) != 0) { SET_ERROR(WOPN_ERR_BAD_MAGIC); return NULL; } GO_FORWARD(11); } if (version == 0) {/* Version code */ if(length < 2) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } version = toUint16LE(cursor); if(version > wopn_latest_version) { SET_ERROR(WOPN_ERR_NEWER_VERSION); return NULL; } GO_FORWARD(2); } {/* Header of WOPN */ uint8_t head[5]; if(length < 5) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } memcpy(head, cursor, 5); count_melodic_banks = toUint16BE(head); count_percussive_banks = toUint16BE(head + 2); GO_FORWARD(5); outFile = WOPN_Init(count_melodic_banks, count_percussive_banks); if(!outFile) { SET_ERROR(WOPN_ERR_OUT_OF_MEMORY); return NULL; } outFile->version = version; outFile->lfo_freq = head[4] & 0xf; if(version >= 2) outFile->chip_type = (head[4] >> 4) & 1; outFile->volume_model = 0; } bankslots_sizes[0] = count_melodic_banks; bankslots[0] = outFile->banks_melodic; bankslots_sizes[1] = count_percussive_banks; bankslots[1] = outFile->banks_percussive; if(version >= 2) /* Bank names and LSB/MSB titles */ { for(i = 0; i < 2; i++) { for(j = 0; j < bankslots_sizes[i]; j++) { if(length < 34) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32); bankslots[i][j].bank_name[32] = '\0'; bankslots[i][j].bank_midi_lsb = cursor[32]; bankslots[i][j].bank_midi_msb = cursor[33]; GO_FORWARD(34); } } } {/* Read instruments data */ uint16_t insSize = 0; if(version > 1) insSize = WOPN_INST_SIZE_V2; else insSize = WOPN_INST_SIZE_V1; for(i = 0; i < 2; i++) { if(length < (insSize * 128) * (size_t)bankslots_sizes[i]) { SET_ERROR(WOPN_ERR_UNEXPECTED_ENDING); return NULL; } for(j = 0; j < bankslots_sizes[i]; j++) { for(k = 0; k < 128; k++) { WOPNInstrument *ins = &bankslots[i][j].ins[k]; WOPN_parseInstrument(ins, cursor, version, 1); GO_FORWARD(insSize); } } } } #undef GO_FORWARD #undef SET_ERROR return outFile; } int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length) { uint16_t version = 0; uint8_t *cursor = (uint8_t *)mem; uint16_t ins_size; if(!cursor) return WOPN_ERR_NULL_POINTER; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } {/* Magic number */ if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(memcmp(cursor, opni_magic1, 11) == 0) version = 1; else if(memcmp(cursor, opni_magic2, 11) != 0) return WOPN_ERR_BAD_MAGIC; GO_FORWARD(11); } if (version == 0) {/* Version code */ if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; version = toUint16LE(cursor); if(version > wopn_latest_version) return WOPN_ERR_NEWER_VERSION; GO_FORWARD(2); } file->version = version; {/* is drum flag */ if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; file->is_drum = *cursor; GO_FORWARD(1); } if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; if(length < ins_size) return WOPN_ERR_UNEXPECTED_ENDING; WOPN_parseInstrument(&file->inst, cursor, version, 0); GO_FORWARD(ins_size); return WOPN_ERR_OK; #undef GO_FORWARD } size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version) { size_t final_size = 0; size_t ins_size = 0; if(version == 0) version = wopn_latest_version; if(!file) return 0; final_size += 11 + 2 + 2 + 2 + 1; /* * Magic number, * Version, * Count of melodic banks, * Count of percussive banks, * Chip specific flags */ if(version >= 2) { /* Melodic banks meta-data */ final_size += (32 + 1 + 1) * file->banks_count_melodic; /* Percussive banks meta-data */ final_size += (32 + 1 + 1) * file->banks_count_percussion; } if(version >= 2) ins_size = WOPN_INST_SIZE_V2; else ins_size = WOPN_INST_SIZE_V1; /* Melodic instruments */ final_size += (ins_size * 128) * file->banks_count_melodic; /* Percussive instruments */ final_size += (ins_size * 128) * file->banks_count_percussion; return final_size; } size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version) { size_t final_size = 0; size_t ins_size = 0; if(version == 0) version = wopn_latest_version; if(!file) return 0; final_size += 11 + 1; /* * Magic number, * is percussive instrument */ /* Version */ if (version > 1) final_size += 2; if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; final_size += ins_size; return final_size; } int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm) { uint8_t *cursor = (uint8_t *)dest_mem; uint16_t ins_size = 0; uint16_t i, j, k; uint16_t banks_melodic = force_gm ? 1 : file->banks_count_melodic; uint16_t banks_percussive = force_gm ? 1 : file->banks_count_percussion; WOPNBank *bankslots[2]; uint16_t bankslots_sizes[2]; if(version == 0) version = wopn_latest_version; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(version > 1) memcpy(cursor, wopn2_magic2, 11); else memcpy(cursor, wopn2_magic1, 11); GO_FORWARD(11); if(version > 1) { if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16LE(version, cursor); GO_FORWARD(2); } if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16BE(banks_melodic, cursor); GO_FORWARD(2); if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16BE(banks_percussive, cursor); GO_FORWARD(2); if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; cursor[0] = file->lfo_freq & 0xf; if (version >= 2) cursor[0] |= (file->chip_type & 1) << 4; GO_FORWARD(1); bankslots[0] = file->banks_melodic; bankslots_sizes[0] = banks_melodic; bankslots[1] = file->banks_percussive; bankslots_sizes[1] = banks_percussive; if(version >= 2) { for(i = 0; i < 2; i++) { for(j = 0; j < bankslots_sizes[i]; j++) { if(length < 34) return WOPN_ERR_UNEXPECTED_ENDING; strncpy((char*)cursor, bankslots[i][j].bank_name, 32); cursor[32] = bankslots[i][j].bank_midi_lsb; cursor[33] = bankslots[i][j].bank_midi_msb; GO_FORWARD(34); } } } {/* Write instruments data */ if(version >= 2) ins_size = WOPN_INST_SIZE_V2; else ins_size = WOPN_INST_SIZE_V1; for(i = 0; i < 2; i++) { if(length < (ins_size * 128) * (size_t)bankslots_sizes[i]) return WOPN_ERR_UNEXPECTED_ENDING; for(j = 0; j < bankslots_sizes[i]; j++) { for(k = 0; k < 128; k++) { WOPNInstrument *ins = &bankslots[i][j].ins[k]; WOPN_writeInstrument(ins, cursor, version, 1); GO_FORWARD(ins_size); } } } } return WOPN_ERR_OK; #undef GO_FORWARD } int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version) { uint8_t *cursor = (uint8_t *)dest_mem; uint16_t ins_size; if(!cursor) return WOPN_ERR_NULL_POINTER; if(version == 0) version = wopn_latest_version; #define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } {/* Magic number */ if(length < 11) return WOPN_ERR_UNEXPECTED_ENDING; if(version > 1) memcpy(cursor, opni_magic2, 11); else memcpy(cursor, opni_magic1, 11); GO_FORWARD(11); } if (version > 1) {/* Version code */ if(length < 2) return WOPN_ERR_UNEXPECTED_ENDING; fromUint16LE(version, cursor); GO_FORWARD(2); } {/* is drum flag */ if(length < 1) return WOPN_ERR_UNEXPECTED_ENDING; *cursor = file->is_drum; GO_FORWARD(1); } if(version > 1) /* Skip sounding delays are not part of single-instrument file * two sizes of uint16_t will be subtracted */ ins_size = WOPN_INST_SIZE_V2 - (sizeof(uint16_t) * 2); else ins_size = WOPN_INST_SIZE_V1; if(length < ins_size) return WOPN_ERR_UNEXPECTED_ENDING; WOPN_writeInstrument(&file->inst, cursor, version, 0); GO_FORWARD(ins_size); return WOPN_ERR_OK; #undef GO_FORWARD } BambooTracker-0.3.5/BambooTracker/format/wopn_file.h000066400000000000000000000210451362177441300224020ustar00rootroot00000000000000/* * Wohlstand's OPN2 Bank File - a bank format to store OPN2 timbre data and setup * * Copyright (c) 2018 Vitaly Novichkov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef WOPN_FILE_H #define WOPN_FILE_H #include #include #ifdef __cplusplus extern "C" { #endif #if !defined(__STDC_VERSION__) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)) \ || defined(__STRICT_ANSI__) || !defined(__cplusplus) typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed short int int16_t; typedef unsigned short int uint16_t; #endif /* Type of chip for which a bank has been designed */ typedef enum WOPN_ChipType { /* The Yamaha OPN2 chip, alias YM2612 YM3438 */ WOPN_Chip_OPN2 = 0, /* The Yamaha OPNA chip, alias YM2608 */ WOPN_Chip_OPNA = 1 } WOPN_ChipType; /* Volume scaling model implemented in the libOPNMIDI */ typedef enum WOPN_VolumeModel { WOPN_VM_Generic = 0 } WOPN_VolumeModel; typedef enum WOPN_InstrumentFlags { /* Is pseudo eight-operator (two 4-operator voices) instrument */ WOPN_Ins_Pseudo8op = 0x01, /* Is a blank instrument entry */ WOPN_Ins_IsBlank = 0x02, /* Mask of the flags range */ WOPN_Ins_ALL_MASK = 0x03 } WOPN_InstrumentFlags; /* Error codes */ typedef enum WOPN_ErrorCodes { WOPN_ERR_OK = 0, /* Magic number is not maching */ WOPN_ERR_BAD_MAGIC, /* Too short file */ WOPN_ERR_UNEXPECTED_ENDING, /* Zero banks count */ WOPN_ERR_INVALID_BANKS_COUNT, /* Version of file is newer than supported by current version of library */ WOPN_ERR_NEWER_VERSION, /* Out of memory */ WOPN_ERR_OUT_OF_MEMORY, /* Given null pointer memory data */ WOPN_ERR_NULL_POINTER } WOPN_ErrorCodes; /* OPN2 Oerators data */ typedef struct WOPNOperator { /* Detune and frequency multiplication register data */ uint8_t dtfm_30; /* Total level register data */ uint8_t level_40; /* Rate scale and attack register data */ uint8_t rsatk_50; /* Amplitude modulation enable and Decay-1 register data */ uint8_t amdecay1_60; /* Decay-2 register data */ uint8_t decay2_70; /* Sustain and Release register data */ uint8_t susrel_80; /* SSG-EG register data */ uint8_t ssgeg_90; } WOPNOperator; /* Instrument entry */ typedef struct WOPNInstrument { /* Title of the instrument */ char inst_name[34]; /* MIDI note key (half-tone) offset for an instrument (or a first voice in pseudo-4-op mode) */ int16_t note_offset; /* Reserved */ int8_t midi_velocity_offset; /* Percussion MIDI base tone number at which this drum will be played */ uint8_t percussion_key_number; /* Enum WOPN_InstrumentFlags */ uint8_t inst_flags; /* Feedback and Algorithm register data */ uint8_t fbalg; /* LFO Sensitivity register data */ uint8_t lfosens; /* Operators register data */ WOPNOperator operators[4]; /* Millisecond delay of sounding while key is on */ uint16_t delay_on_ms; /* Millisecond delay of sounding after key off */ uint16_t delay_off_ms; } WOPNInstrument; /* Bank entry */ typedef struct WOPNBank { /* Name of bank */ char bank_name[33]; /* MIDI Bank LSB code */ uint8_t bank_midi_lsb; /* MIDI Bank MSB code */ uint8_t bank_midi_msb; /* Instruments data of this bank */ WOPNInstrument ins[128]; } WOPNBank; /* Instrument data file */ typedef struct OPNIFile { /* Version of instrument file */ uint16_t version; /* Is this a percussion instrument */ uint8_t is_drum; /* Instrument data */ WOPNInstrument inst; } OPNIFile; /* Bank data file */ typedef struct WOPNFile { /* Version of bank file */ uint16_t version; /* Count of melodic banks in this file */ uint16_t banks_count_melodic; /* Count of percussion banks in this file */ uint16_t banks_count_percussion; /* Chip global LFO enable flag and frequency register data */ uint8_t lfo_freq; /* Chip type this bank is designed for */ uint8_t chip_type; /* Reserved (Enum WOPN_VolumeModel) */ uint8_t volume_model; /* dynamically allocated data Melodic banks array */ WOPNBank *banks_melodic; /* dynamically allocated data Percussive banks array */ WOPNBank *banks_percussive; } WOPNFile; /** * @brief Initialize blank WOPN data structure with allocated bank data * @param melodic_banks Count of melodic banks * @param percussive_banks Count of percussive banks * @return pointer to heap-allocated WOPN data structure or NULL when out of memory or incorrectly given banks counts */ extern WOPNFile *WOPN_Init(uint16_t melodic_banks, uint16_t percussive_banks); /** * @brief Clean up WOPN data file (all allocated bank arrays will be fried too) * @param file pointer to heap-allocated WOPN data structure */ extern void WOPN_Free(WOPNFile *file); /** * @brief Compare two bank entries * @param bank1 First bank * @param bank2 Second bank * @return 1 if banks are equal or 0 if there are different */ extern int WOPN_BanksCmp(const WOPNFile *bank1, const WOPNFile *bank2); /** * @brief Load WOPN bank file from the memory. * WOPN data structure will be allocated. (don't forget to clear it with WOPN_Free() after use!) * @param mem Pointer to memory block contains raw WOPN bank file data * @param length Length of given memory block * @param error pointer to integer to return an error code. Pass NULL if you don't want to use error codes. * @return Heap-allocated WOPN file data structure or NULL if any error has occouped */ extern WOPNFile *WOPN_LoadBankFromMem(void *mem, size_t length, int *error); /** * @brief Load WOPI instrument file from the memory. * You must allocate OPNIFile structure by yourself and give the pointer to it. * @param file Pointer to destination OPNIFile structure to fill it with parsed data. * @param mem Pointer to memory block contains raw WOPI instrument file data * @param length Length of given memory block * @return 0 if no errors occouped, or an error code of WOPN_ErrorCodes enumeration */ extern int WOPN_LoadInstFromMem(OPNIFile *file, void *mem, size_t length); /** * @brief Calculate the size of the output memory block * @param file Heap-allocated WOPN file data structure * @param version Destination version of the file * @return Size of the raw WOPN file data */ extern size_t WOPN_CalculateBankFileSize(WOPNFile *file, uint16_t version); /** * @brief Calculate the size of the output memory block * @param file Pointer to WOPI file data structure * @param version Destination version of the file * @return Size of the raw WOPI file data */ extern size_t WOPN_CalculateInstFileSize(OPNIFile *file, uint16_t version); /** * @brief Write raw WOPN into given memory block * @param file Heap-allocated WOPN file data structure * @param dest_mem Destination memory block pointer * @param length Length of destination memory block * @param version Wanted WOPN version * @param force_gm Force GM set in saved bank file * @return Error code or 0 on success */ extern int WOPN_SaveBankToMem(WOPNFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm); /** * @brief Write raw WOPI into given memory block * @param file Pointer to WOPI file data structure * @param dest_mem Destination memory block pointer * @param length Length of destination memory block * @param version Wanted WOPI version * @return Error code or 0 on success */ extern int WOPN_SaveInstToMem(OPNIFile *file, void *dest_mem, size_t length, uint16_t version); #ifdef __cplusplus } #endif #endif /* WOPN_FILE_H */ BambooTracker-0.3.5/BambooTracker/gui/000077500000000000000000000000001362177441300175415ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/color_palette.cpp000066400000000000000000000104421362177441300231020ustar00rootroot00000000000000#include "color_palette.hpp" ColorPalette::ColorPalette() { // Instrument list ilistTextColor = QColor::fromRgb(255, 255, 255, 255); ilistBackColor = QColor::fromRgb(0, 0, 0, 255); ilistSelBackColor = QColor::fromRgb(110, 90, 140, 255); ilistHovBackColor = QColor::fromRgb(255, 255, 255, 75); ilistHovSelBackColor = QColor::fromRgb(140, 120, 170, 255); // Instrument editor instFMEnvLine1Color = QColor::fromRgb(242, 38, 19, 255); instFMEnvLine2Color = QColor::fromRgb(46, 204, 113, 255); instFMEnvLine3Color = QColor::fromRgb(38, 38, 255, 255); instFMEnvGridColor = QColor::fromRgb(40, 40, 40, 63); instFMEnvBackColor = QColor::fromRgb(255, 255, 255, 0); instFMEnvBorderColor = QColor::fromRgb(125, 125, 125, 255); instFMAlForeColor = QColor::fromRgb(107, 185, 240, 255); instFMAlBackColor = QColor::fromRgb(255, 255, 255, 0); instSeqLoopBackColor = QColor::fromRgb(25, 25, 25, 255); instSeqReleaseBackColor = QColor::fromRgb(0, 0, 0, 255); instSeqLoopColor = QColor::fromRgb(210, 40, 180, 127); instSeqReleaseColor = QColor::fromRgb(40, 170, 200, 127); instSeqLoopEdgeColor = QColor::fromRgb(180, 20, 180, 127); instSeqReleaseEdgeColor = QColor::fromRgb(40, 170, 150, 127); instSeqTagColor = QColor::fromRgb(255, 255, 255, 255); instSeqHovColor = QColor::fromRgb(255, 255, 255, 63); instSeqLoopTextColor = QColor::fromRgb(24, 223, 172, 255); instSeqReleaseTextColor = QColor::fromRgb(24, 223, 172, 255); instSeqCellColor = QColor::fromRgb(38, 183, 173, 255); instSeqCellTextColor = QColor::fromRgb(255, 255, 255); instSeqBorderColor = QColor::fromRgb(50, 50, 50, 255); instSeqMaskColor = QColor::fromRgb(0, 0, 0, 128); instSeqOddColColor = QColor::fromRgb(255, 255, 255, 31); // Tone/Noise editor tnToneCellColor = QColor::fromRgb(225, 209, 47, 255); tnToneTextColor = QColor::fromRgb(255, 255, 126, 255); tnNoiseCellColor = QColor::fromRgb(210, 40, 180, 255); tnNoiseTextColor = QColor::fromRgb(240, 110, 220, 255); tnToneBackColor = QColor::fromRgb(0, 0, 0, 255); tnNoiseBackColor = QColor::fromRgb(25, 25, 25, 255); // Order list odrDefTextColor = QColor::fromRgb(180, 180, 180, 255); odrDefRowColor = QColor::fromRgb(40, 40, 80, 255); odrCurTextColor = QColor::fromRgb(255, 255, 255, 255); odrCurRowColor = QColor::fromRgb(110, 90, 140, 255); odrCurEditRowColor = QColor::fromRgb(140, 90, 110, 255); odrCurCellColor = QColor::fromRgb(255, 255, 255, 127); odrPlayRowColor = QColor::fromRgb(90, 90, 140, 255); odrSelCellColor = QColor::fromRgb(100, 100, 200, 192); odrHovCellColor = QColor::fromRgb(255, 255, 255, 64); odrRowNumColor = QColor::fromRgb(255, 200, 180, 255); odrHeaderTextColor = QColor::fromRgb(240, 240, 200, 255); odrHeaderRowColor = QColor::fromRgb(60, 60, 60, 255); odrBorderColor = QColor::fromRgb(120, 120, 120, 255); odrBackColor = QColor::fromRgb(0, 0, 0, 255); // Pattern editor ptnDefTextColor = QColor::fromRgb(180, 180, 180, 255); ptnDefStepColor = QColor::fromRgb(0, 0, 40, 255); ptnHl1StepColor = QColor::fromRgb(30, 40, 70, 255); ptnHl2StepColor = QColor::fromRgb(60, 60, 100, 255); ptnCurTextColor = QColor::fromRgb(255, 255, 255, 255); ptnCurStepColor = QColor::fromRgb(110, 90, 140, 255); ptnCurEditStepColor = QColor::fromRgb(140, 90, 110, 255); ptnCurCellColor = QColor::fromRgb(255, 255, 255, 127); ptnPlayStepColor = QColor::fromRgb(90, 90, 140, 255); ptnSelCellColor = QColor::fromRgb(100, 100, 200, 192); ptnHovCellColor = QColor::fromRgb(255, 255, 255, 64); ptnDefStepNumColor = QColor::fromRgb(255, 200, 180, 255); ptnHl1StepNumColor = QColor::fromRgb(255, 140, 160, 255); ptnHl2StepNumColor = QColor::fromRgb(255, 140, 160, 255); ptnNoteColor = QColor::fromRgb(210, 230, 64, 255); ptnInstColor = QColor::fromRgb(82, 179, 217, 255); ptnVolColor = QColor::fromRgb(226, 156, 80, 255); ptnEffColor = QColor::fromRgb(42, 187, 155, 255); ptnErrorColor = QColor::fromRgb(255, 0, 0, 255); ptnHeaderTextColor = QColor::fromRgb(240, 240, 200, 255); ptnHeaderRowColor = QColor::fromRgb(60, 60, 60, 255); ptnMaskColor = QColor::fromRgb(0, 0, 0, 127); ptnBorderColor = QColor::fromRgb(120, 120, 120, 255); ptnMuteColor = QColor::fromRgb(255, 0, 0, 255); ptnUnmuteColor = QColor::fromRgb(0, 255, 0, 255); ptnBackColor = QColor::fromRgb(0, 0, 0, 255); // Wave visual wavBackColor = QColor::fromRgb(0, 0, 33, 255); wavDrawColor = QColor::fromRgb(82, 179, 217, 255); } BambooTracker-0.3.5/BambooTracker/gui/color_palette.hpp000066400000000000000000000037331362177441300231140ustar00rootroot00000000000000#ifndef COLOR_PALETTE_HPP #define COLOR_PALETTE_HPP #include class ColorPalette { public: ColorPalette(); // Instrument list QColor ilistTextColor, ilistBackColor; QColor ilistSelBackColor; QColor ilistHovBackColor; QColor ilistHovSelBackColor; // Instrument editor QColor instFMEnvLine1Color, instFMEnvLine2Color, instFMEnvLine3Color; QColor instFMEnvGridColor; QColor instFMEnvBackColor, instFMEnvBorderColor; QColor instFMAlForeColor, instFMAlBackColor; QColor instSeqTagColor; QColor instSeqHovColor; QColor instSeqLoopBackColor, instSeqLoopColor, instSeqLoopEdgeColor; QColor instSeqReleaseBackColor, instSeqReleaseColor, instSeqReleaseEdgeColor; QColor instSeqLoopTextColor, instSeqReleaseTextColor; QColor instSeqCellColor, instSeqCellTextColor; QColor instSeqBorderColor; QColor instSeqMaskColor; QColor instSeqOddColColor; // Tone/Noise editor QColor tnToneCellColor, tnToneTextColor; QColor tnNoiseCellColor, tnNoiseTextColor; QColor tnToneBackColor, tnNoiseBackColor; // Order list QColor odrDefTextColor, odrDefRowColor; QColor odrCurTextColor, odrCurRowColor; QColor odrCurEditRowColor; QColor odrCurCellColor; QColor odrPlayRowColor; QColor odrSelCellColor; QColor odrHovCellColor; QColor odrRowNumColor; QColor odrHeaderTextColor, odrHeaderRowColor; QColor odrBorderColor; QColor odrBackColor; // Pattern editor QColor ptnDefTextColor, ptnDefStepColor, ptnHl1StepColor, ptnHl2StepColor; QColor ptnCurTextColor, ptnCurStepColor, ptnCurEditStepColor, ptnCurCellColor; QColor ptnPlayStepColor; QColor ptnSelCellColor; QColor ptnHovCellColor; QColor ptnDefStepNumColor, ptnHl1StepNumColor, ptnHl2StepNumColor; QColor ptnNoteColor, ptnInstColor, ptnVolColor, ptnEffColor; QColor ptnErrorColor; QColor ptnHeaderTextColor, ptnHeaderRowColor; QColor ptnMaskColor; QColor ptnBorderColor; QColor ptnMuteColor, ptnUnmuteColor; QColor ptnBackColor; // Wave visual QColor wavBackColor; QColor wavDrawColor; }; #endif // COLOR_PALETTE_HPP BambooTracker-0.3.5/BambooTracker/gui/color_palette_handler.cpp000066400000000000000000000275211362177441300246050ustar00rootroot00000000000000#include "color_palette_handler.hpp" #include #include // config path (*nix): ~/.config//.ini const QString ColorPaletteHandler::organization = "BambooTracker"; const QString ColorPaletteHandler::application = "BambooTracker"; ColorPaletteHandler::ColorPaletteHandler() {} bool ColorPaletteHandler::savePalette(std::weak_ptr palette) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ColorPaletteHandler::organization, ColorPaletteHandler::application); save(settings, palette); return true; } catch (...) { return false; } } bool ColorPaletteHandler::savePalette(QString file, std::weak_ptr palette) { try { QSettings settings(file, QSettings::IniFormat); save(settings, palette); return true; } catch (...) { return false; } } void ColorPaletteHandler::save(QSettings& settings, std::weak_ptr palette) { std::shared_ptr paletteLocked = palette.lock(); settings.beginGroup("PatternEditor"); settings.setValue("defaultStepText", paletteLocked->ptnDefTextColor.name(QColor::HexArgb)); settings.setValue("defaultStepBackground", paletteLocked->ptnDefStepColor.name(QColor::HexArgb)); settings.setValue("highlightedStep1Background", paletteLocked->ptnHl1StepColor.name(QColor::HexArgb)); settings.setValue("highlightedStep2Background", paletteLocked->ptnHl2StepColor.name(QColor::HexArgb)); settings.setValue("currentStepText", paletteLocked->ptnCurTextColor.name(QColor::HexArgb)); settings.setValue("currentStepBackground", paletteLocked->ptnCurStepColor.name(QColor::HexArgb)); settings.setValue("currentEditingStepBackground", paletteLocked->ptnCurEditStepColor.name(QColor::HexArgb)); settings.setValue("currentCellBackground", paletteLocked->ptnCurCellColor.name(QColor::HexArgb)); settings.setValue("currentPlayingStepBackground", paletteLocked->ptnPlayStepColor.name(QColor::HexArgb)); settings.setValue("selectionBackground", paletteLocked->ptnSelCellColor.name(QColor::HexArgb)); settings.setValue("hoveredCellBackground", paletteLocked->ptnHovCellColor.name(QColor::HexArgb)); settings.setValue("defaultStepNumber", paletteLocked->ptnDefStepNumColor.name(QColor::HexArgb)); settings.setValue("highlightedStep1Number", paletteLocked->ptnHl1StepNumColor.name(QColor::HexArgb)); settings.setValue("highlightedStep2Number", paletteLocked->ptnHl2StepNumColor.name(QColor::HexArgb)); settings.setValue("noteText", paletteLocked->ptnNoteColor.name(QColor::HexArgb)); settings.setValue("instrumentText", paletteLocked->ptnInstColor.name(QColor::HexArgb)); settings.setValue("volumeText", paletteLocked->ptnVolColor.name(QColor::HexArgb)); settings.setValue("effectText", paletteLocked->ptnEffColor.name(QColor::HexArgb)); settings.setValue("errorText", paletteLocked->ptnErrorColor.name(QColor::HexArgb)); settings.setValue("headerText", paletteLocked->ptnHeaderTextColor.name(QColor::HexArgb)); settings.setValue("headerBackground", paletteLocked->ptnHeaderRowColor.name(QColor::HexArgb)); settings.setValue("mask", paletteLocked->ptnMaskColor.name(QColor::HexArgb)); settings.setValue("border", paletteLocked->ptnBorderColor.name(QColor::HexArgb)); settings.setValue("mute", paletteLocked->ptnMuteColor.name(QColor::HexArgb)); settings.setValue("unmute", paletteLocked->ptnUnmuteColor.name(QColor::HexArgb)); settings.setValue("background", paletteLocked->ptnBackColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("OrderList"); settings.setValue("defaultRowText", paletteLocked->odrDefTextColor.name(QColor::HexArgb)); settings.setValue("defaultRowBackground", paletteLocked->odrDefRowColor.name(QColor::HexArgb)); settings.setValue("currentRowText", paletteLocked->odrCurTextColor.name(QColor::HexArgb)); settings.setValue("currentRowBackground", paletteLocked->odrCurRowColor.name(QColor::HexArgb)); settings.setValue("currentEditingRowBackground", paletteLocked->odrCurEditRowColor.name(QColor::HexArgb)); settings.setValue("currentCellBackground", paletteLocked->odrCurCellColor.name(QColor::HexArgb)); settings.setValue("currentPlayingRowBackground", paletteLocked->odrPlayRowColor.name(QColor::HexArgb)); settings.setValue("selectionBackground", paletteLocked->odrSelCellColor.name(QColor::HexArgb)); settings.setValue("hoveredCellBackground", paletteLocked->odrHovCellColor.name(QColor::HexArgb)); settings.setValue("rowNumber", paletteLocked->odrRowNumColor.name(QColor::HexArgb)); settings.setValue("headerText", paletteLocked->odrHeaderTextColor.name(QColor::HexArgb)); settings.setValue("headerBackground", paletteLocked->odrHeaderRowColor.name(QColor::HexArgb)); settings.setValue("border", paletteLocked->odrBorderColor.name(QColor::HexArgb)); settings.setValue("background", paletteLocked->odrBackColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("InstrumentList"); settings.setValue("defaultText", paletteLocked->ilistTextColor.name(QColor::HexArgb)); settings.setValue("background", paletteLocked->ilistBackColor.name(QColor::HexArgb)); settings.setValue("selectedBackground", paletteLocked->ilistSelBackColor.name(QColor::HexArgb)); settings.setValue("hoveredBackground", paletteLocked->ilistHovBackColor.name(QColor::HexArgb)); settings.setValue("selectedHoveredBackground", paletteLocked->ilistHovSelBackColor.name(QColor::HexArgb)); settings.endGroup(); settings.beginGroup("Oscilloscope"); settings.setValue("background", paletteLocked->wavBackColor.name(QColor::HexArgb)); settings.setValue("foreground", paletteLocked->wavDrawColor.name(QColor::HexArgb)); settings.endGroup(); } bool ColorPaletteHandler::loadPalette(std::weak_ptr palette) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ColorPaletteHandler::organization, ColorPaletteHandler::application); load(settings, palette); return true; } catch (...) { return false; } } bool ColorPaletteHandler::loadPalette(QString file, std::weak_ptr palette) { try { QSettings settings(file, QSettings::IniFormat); load(settings, palette); return true; } catch (...) { return false; } } void ColorPaletteHandler::load(QSettings& settings, std::weak_ptr palette) { std::shared_ptr paletteLocked = palette.lock(); settings.beginGroup("PatternEditor"); paletteLocked->ptnDefTextColor = settings.value("defaultStepText", paletteLocked->ptnDefTextColor).value(); paletteLocked->ptnDefStepColor = settings.value("defaultStepBackground", paletteLocked->ptnDefStepColor).value(); paletteLocked->ptnHl1StepColor = settings.value("highlightedStep1Background", paletteLocked->ptnHl1StepColor).value(); paletteLocked->ptnHl2StepColor = settings.value("highlightedStep2Background", paletteLocked->ptnHl2StepColor).value(); paletteLocked->ptnCurTextColor = settings.value("currentStepText", paletteLocked->ptnCurTextColor).value(); paletteLocked->ptnCurStepColor = settings.value("currentStepBackground", paletteLocked->ptnCurStepColor).value(); paletteLocked->ptnCurEditStepColor = settings.value("currentEditingStepBackground", paletteLocked->ptnCurEditStepColor).value(); paletteLocked->ptnCurCellColor = settings.value("currentCellBackground", paletteLocked->ptnCurCellColor).value(); paletteLocked->ptnPlayStepColor = settings.value("currentPlayingStepBackground", paletteLocked->ptnPlayStepColor).value(); paletteLocked->ptnSelCellColor = settings.value("selectionBackground", paletteLocked->ptnSelCellColor).value(); paletteLocked->ptnHovCellColor = settings.value("hoveredCellBackground", paletteLocked->ptnHovCellColor).value(); paletteLocked->ptnDefStepNumColor = settings.value("defaultStepNumber", paletteLocked->ptnDefStepNumColor).value(); paletteLocked->ptnHl1StepNumColor = settings.value("highlightedStep1Number", paletteLocked->ptnHl1StepNumColor).value(); paletteLocked->ptnHl2StepNumColor = settings.value("highlightedStep2Number", paletteLocked->ptnHl2StepNumColor).value(); paletteLocked->ptnNoteColor = settings.value("noteText", paletteLocked->ptnNoteColor).value(); paletteLocked->ptnInstColor = settings.value("instrumentText", paletteLocked->ptnInstColor).value(); paletteLocked->ptnVolColor = settings.value("volumeText", paletteLocked->ptnVolColor).value(); paletteLocked->ptnEffColor = settings.value("effectText", paletteLocked->ptnEffColor).value(); paletteLocked->ptnErrorColor = settings.value("errorText", paletteLocked->ptnErrorColor).value(); paletteLocked->ptnHeaderTextColor = settings.value("headerText", paletteLocked->ptnHeaderTextColor).value(); paletteLocked->ptnHeaderRowColor = settings.value("headerBackground", paletteLocked->ptnHeaderRowColor).value(); paletteLocked->ptnMaskColor = settings.value("mask", paletteLocked->ptnMaskColor).value(); paletteLocked->ptnBorderColor = settings.value("border", paletteLocked->ptnBorderColor).value(); paletteLocked->ptnMuteColor = settings.value("mute", paletteLocked->ptnMuteColor).value(); paletteLocked->ptnUnmuteColor = settings.value("unmute", paletteLocked->ptnUnmuteColor).value(); paletteLocked->ptnBackColor = settings.value("background", paletteLocked->ptnBackColor).value(); settings.endGroup(); settings.beginGroup("OrderList"); paletteLocked->odrDefTextColor = settings.value("defaultRowText", paletteLocked->odrDefTextColor).value(); paletteLocked->odrDefRowColor = settings.value("defaultRowBackground", paletteLocked->odrDefRowColor).value(); paletteLocked->odrCurTextColor = settings.value("currentRowText", paletteLocked->odrCurTextColor).value(); paletteLocked->odrCurRowColor = settings.value("currentRowBackground", paletteLocked->odrCurRowColor).value(); paletteLocked->odrCurEditRowColor = settings.value("currentEditingRowBackground", paletteLocked->odrCurEditRowColor).value(); paletteLocked->odrCurCellColor = settings.value("currentCellBackground", paletteLocked->odrCurCellColor).value(); paletteLocked->odrPlayRowColor = settings.value("currentPlayingRowBackground", paletteLocked->odrPlayRowColor).value(); paletteLocked->odrSelCellColor = settings.value("selectionBackground", paletteLocked->odrSelCellColor).value(); paletteLocked->odrHovCellColor = settings.value("hoveredCellBackground", paletteLocked->odrHovCellColor).value(); paletteLocked->odrRowNumColor = settings.value("rowNumber", paletteLocked->odrRowNumColor).value(); paletteLocked->odrHeaderTextColor = settings.value("headerText", paletteLocked->odrHeaderTextColor).value(); paletteLocked->odrHeaderRowColor = settings.value("headerBackground", paletteLocked->odrHeaderRowColor).value(); paletteLocked->odrBorderColor = settings.value("border", paletteLocked->odrBorderColor).value(); paletteLocked->odrBackColor = settings.value("background", paletteLocked->odrBackColor).value(); settings.endGroup(); settings.beginGroup("InstrumentList"); paletteLocked->ilistTextColor = settings.value("defaultText", paletteLocked->ilistTextColor).value(); paletteLocked->ilistBackColor = settings.value("background", paletteLocked->ilistBackColor).value(); paletteLocked->ilistSelBackColor = settings.value("selectedBackground", paletteLocked->ilistSelBackColor).value(); paletteLocked->ilistHovBackColor = settings.value("hoveredBackground", paletteLocked->ilistHovBackColor).value(); paletteLocked->ilistHovSelBackColor = settings.value("selectedHoveredBackground", paletteLocked->ilistHovSelBackColor).value(); settings.endGroup(); settings.beginGroup("Oscilloscope"); paletteLocked->wavBackColor = settings.value("background", paletteLocked->wavBackColor).value(); paletteLocked->wavDrawColor = settings.value("foreground", paletteLocked->wavDrawColor).value(); settings.endGroup(); } BambooTracker-0.3.5/BambooTracker/gui/color_palette_handler.hpp000066400000000000000000000013741362177441300246100ustar00rootroot00000000000000#ifndef COLORPALETTEHANDLER_HPP #define COLORPALETTEHANDLER_HPP #include #include #include "color_palette.hpp" class QSettings; class ColorPaletteHandler { public: static bool savePalette(std::weak_ptr palette); static bool savePalette(QString file, std::weak_ptr palette); static bool loadPalette(std::weak_ptr palette); static bool loadPalette(QString file, std::weak_ptr palette); private: ColorPaletteHandler(); const static QString organization; const static QString application; static void save(QSettings& settings, std::weak_ptr palette); static void load(QSettings& settings, std::weak_ptr palette); }; #endif // COLORPALETTEHANDLER_HPP BambooTracker-0.3.5/BambooTracker/gui/command/000077500000000000000000000000001362177441300211575ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/commands_qt.hpp000066400000000000000000000006261362177441300242010ustar00rootroot00000000000000#ifndef COMMANDS_QT_HPP #define COMMANDS_QT_HPP /********** Instrument edit **********/ #include "./instrument/add_instrument_qt_command.hpp" #include "./instrument/remove_instrument_qt_command.hpp" #include "./instrument/change_instrument_name_qt_command.hpp" #include "./instrument/clone_instrument_qt_command.hpp" #include "./instrument/deep_clone_instrument_qt_command.hpp" #endif // COMMANDS_QT_HPP BambooTracker-0.3.5/BambooTracker/gui/command/instrument/000077500000000000000000000000001362177441300233675ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/instrument/add_instrument_qt_command.cpp000066400000000000000000000033641362177441300313230ustar00rootroot00000000000000#include "add_instrument_qt_command.hpp" #include #include #include #include #include #include "command_id.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" AddInstrumentQtCommand::AddInstrumentQtCommand(QListWidget *list, int num, QString name, SoundSource source, std::weak_ptr formMan, QUndoCommand *parent) : QUndoCommand(parent), list_(list), num_(num), name_(name), source_(source), formMan_(formMan) {} void AddInstrumentQtCommand::undo() { auto&& item = list_->takeItem(num_); delete item; formMan_.lock()->remove(num_); if (QApplication::clipboard()->text().contains( QRegularExpression("^.+_INSTRUMENT:"+QString::number(num_), QRegularExpression::DotMatchesEverythingOption))) { QApplication::clipboard()->clear(); } } void AddInstrumentQtCommand::redo() { QListWidgetItem *item; std::unique_ptr form; auto title = QString("%1: %2").arg(num_, 2, 16, QChar('0')).toUpper().arg(name_); switch (source_) { case SoundSource::FM: item = new QListWidgetItem(QIcon(":/icon/inst_fm"), title); form = std::make_unique(num_); break; case SoundSource::SSG: item = new QListWidgetItem(QIcon(":/icon/inst_ssg"), title); form = std::make_unique(num_); break; default: return; } // KEEP CODE ORDER // formMan_.lock()->add(num_, std::move(form), name_, source_); item->setSizeHint(QSize(130, 17)); item->setData(Qt::UserRole, num_); list_->insertItem(num_, item); //----------// } int AddInstrumentQtCommand::id() const { return CommandId::AddInstrument; } BambooTracker-0.3.5/BambooTracker/gui/command/instrument/add_instrument_qt_command.hpp000066400000000000000000000013721362177441300313250ustar00rootroot00000000000000#ifndef ADD_INSTRUMENT_QT_COMMAND_HPP #define ADD_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include #include #include "gui/instrument_editor/instrument_form_manager.hpp" #include "misc.hpp" class AddInstrumentQtCommand : public QUndoCommand { public: AddInstrumentQtCommand(QListWidget *list, int num, QString name, SoundSource source, std::weak_ptr formMan, QUndoCommand *parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget *list_; int num_; QString name_; SoundSource source_; std::weak_ptr formMan_; }; #endif // ADD_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/instrument/change_instrument_name_qt_command.cpp000066400000000000000000000037721362177441300330230ustar00rootroot00000000000000#include "change_instrument_name_qt_command.hpp" #include "command_id.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" #include "misc.hpp" ChangeInstrumentNameQtCommand::ChangeInstrumentNameQtCommand(QListWidget *list, int num, int row, std::weak_ptr formMan, QString oldName, QString newName, QUndoCommand *parent) : QUndoCommand(parent), list_(list), num_(num), row_(row), formMan_(formMan), oldName_(oldName), newName_(newName) { source_ = static_cast(formMan.lock()->getForm(num)->property("SoundSource").toInt()); } void ChangeInstrumentNameQtCommand::redo() { auto item = list_->item(row_); auto title = QString("%1: %2").arg(num_, 2, 16, QChar('0')).toUpper().arg(newName_); item->setText(title); formMan_.lock()->setFormInstrumentName(num_, newName_); auto form = formMan_.lock()->getForm(num_).get(); switch (source_) { case SoundSource::FM: { auto fmForm = qobject_cast(form); fmForm->setWindowTitle(title); break; } case SoundSource::SSG: { auto ssgForm = qobject_cast(form); ssgForm->setWindowTitle(title); break; } default: break; } } void ChangeInstrumentNameQtCommand::undo() { auto item = list_->item(row_); auto title = QString("%1: %2").arg(num_, 2, 16, QChar('0')).toUpper().arg(oldName_); item->setText(title); formMan_.lock()->setFormInstrumentName(num_, oldName_); auto form = formMan_.lock()->getForm(num_).get(); switch (source_) { case SoundSource::FM: { auto fmForm = qobject_cast(form); fmForm->setWindowTitle(title); break; } case SoundSource::SSG: { auto ssgForm = qobject_cast(form); ssgForm->setWindowTitle(title); break; } default: break; } } int ChangeInstrumentNameQtCommand::id() const { return CommandId::ChangeInstrumentName; } BambooTracker-0.3.5/BambooTracker/gui/command/instrument/change_instrument_name_qt_command.hpp000066400000000000000000000014711362177441300330220ustar00rootroot00000000000000#ifndef CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP #define CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP #include #include #include #include #include "gui/instrument_editor/instrument_form_manager.hpp" #include "misc.hpp" class ChangeInstrumentNameQtCommand : public QUndoCommand { public: ChangeInstrumentNameQtCommand(QListWidget *list, int num, int row, std::weak_ptr formMan, QString oldName, QString newName, QUndoCommand* parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget *list_; int num_; int row_; std::weak_ptr formMan_; QString oldName_, newName_; SoundSource source_; }; #endif // CHANGE_INSTRUMENT_NAME_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/instrument/clone_instrument_qt_command.cpp000066400000000000000000000031141362177441300316640ustar00rootroot00000000000000#include "clone_instrument_qt_command.hpp" #include "command_id.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" CloneInstrumentQtCommand::CloneInstrumentQtCommand(QListWidget *list, int num, int refNum, std::weak_ptr formMan, QUndoCommand *parent) : QUndoCommand(parent), list_(list), cloneNum_(num), refNum_(refNum), formMan_(formMan) { source_ = formMan.lock()->getFormInstrumentSoundSource(refNum); } void CloneInstrumentQtCommand::redo() { QListWidgetItem *item; std::unique_ptr form; QString refName = formMan_.lock()->getFormInstrumentName(refNum_); auto title = QString("%1: %2") .arg(cloneNum_, 2, 16, QChar('0')).toUpper() .arg(refName); switch (source_) { case SoundSource::FM: item = new QListWidgetItem(QIcon(":/icon/inst_fm"), title); form = std::make_unique(cloneNum_); break; case SoundSource::SSG: item = new QListWidgetItem(QIcon(":/icon/inst_ssg"), title); form = std::make_unique(cloneNum_); break; default: return; } // KEEP CODE ORDER // formMan_.lock()->add(cloneNum_, std::move(form), refName, source_); item->setSizeHint(QSize(130, 17)); item->setData(Qt::UserRole, cloneNum_); list_->insertItem(cloneNum_, item); //----------// } void CloneInstrumentQtCommand::undo() { auto&& item = list_->takeItem(cloneNum_); delete item; formMan_.lock()->remove(cloneNum_); } int CloneInstrumentQtCommand::id() const { return CommandId::CloneInstrument; } BambooTracker-0.3.5/BambooTracker/gui/command/instrument/clone_instrument_qt_command.hpp000066400000000000000000000013201362177441300316660ustar00rootroot00000000000000#ifndef CLONE_INSTRUMENT_QT_COMMAND_H #define CLONE_INSTRUMENT_QT_COMMAND_H #include #include #include #include "gui/instrument_editor/instrument_form_manager.hpp" #include "misc.hpp" class CloneInstrumentQtCommand : public QUndoCommand { public: CloneInstrumentQtCommand(QListWidget *list, int num, int refNum, std::weak_ptr formMan, QUndoCommand* parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget* list_; int cloneNum_, refNum_; std::weak_ptr formMan_; SoundSource source_; }; #endif // CLONE_INSTRUMENT_QT_COMMAND_H BambooTracker-0.3.5/BambooTracker/gui/command/instrument/deep_clone_instrument_qt_command.cpp000066400000000000000000000031511362177441300326620ustar00rootroot00000000000000#include "deep_clone_instrument_qt_command.hpp" #include "command_id.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" DeepCloneInstrumentQtCommand::DeepCloneInstrumentQtCommand(QListWidget *list, int num, int refNum, std::weak_ptr formMan, QUndoCommand *parent) : QUndoCommand(parent), list_(list), cloneNum_(num), refNum_(refNum), formMan_(formMan) { source_ = formMan.lock()->getFormInstrumentSoundSource(refNum); } void DeepCloneInstrumentQtCommand::redo() { QListWidgetItem *item; std::unique_ptr form; QString refName = formMan_.lock()->getFormInstrumentName(refNum_); auto title = QString("%1: %2") .arg(cloneNum_, 2, 16, QChar('0')).toUpper() .arg(refName); switch (source_) { case SoundSource::FM: item = new QListWidgetItem(QIcon(":/icon/inst_fm"), title); form = std::make_unique(cloneNum_); break; case SoundSource::SSG: item = new QListWidgetItem(QIcon(":/icon/inst_ssg"), title); form = std::make_unique(cloneNum_); break; default: return; } // KEEP CODE ORDER // formMan_.lock()->add(cloneNum_, std::move(form), refName, source_); item->setSizeHint(QSize(130, 17)); item->setData(Qt::UserRole, cloneNum_); list_->insertItem(cloneNum_, item); //----------// } void DeepCloneInstrumentQtCommand::undo() { auto&& item = list_->takeItem(cloneNum_); delete item; formMan_.lock()->remove(cloneNum_); } int DeepCloneInstrumentQtCommand::id() const { return CommandId::DeepCloneInstrument; } BambooTracker-0.3.5/BambooTracker/gui/command/instrument/deep_clone_instrument_qt_command.hpp000066400000000000000000000013451362177441300326720ustar00rootroot00000000000000#ifndef DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP #define DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include "gui/instrument_editor/instrument_form_manager.hpp" #include "misc.hpp" class DeepCloneInstrumentQtCommand : public QUndoCommand { public: DeepCloneInstrumentQtCommand(QListWidget *list, int num, int refNum, std::weak_ptr formMan, QUndoCommand* parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget* list_; int cloneNum_, refNum_; std::weak_ptr formMan_; SoundSource source_; }; #endif // DEEP_CLONE_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/instrument/paste_instrument_qt_command_copy.h000066400000000000000000000014341362177441300324020ustar00rootroot00000000000000#ifndef PASTE_INSTRUMENT_QT_COMMAND_H #define PASTE_INSTRUMENT_QT_COMMAND_H #include #include #include #include #include #include "misc.hpp" class PasteInstrumentQtCommand : public QUndoCommand { public: PasteInstrumentQtCommand(QListWidget *list, int oldRow, int refRow, int oldNum, int refNum, std::map>& map, SoundSource source, QUndoCommand* parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget* list_; int oldRow_, refRow_; int oldNum_, refNum_; QString oldName_, refName_; std::map>& map_; SoundSource source_; }; #endif // PASTE_INSTRUMENT_QT_COMMAND_H BambooTracker-0.3.5/BambooTracker/gui/command/instrument/remove_instrument_qt_command.cpp000066400000000000000000000035201362177441300320620ustar00rootroot00000000000000#include "remove_instrument_qt_command.hpp" #include #include #include #include #include #include "command_id.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" RemoveInstrumentQtCommand::RemoveInstrumentQtCommand(QListWidget *list, int num, int row, std::weak_ptr formMan, QUndoCommand *parent) : QUndoCommand(parent), list_(list), num_(num), row_(row), formMan_(formMan) { source_ = formMan.lock()->getFormInstrumentSoundSource(num); } void RemoveInstrumentQtCommand::undo() { QListWidgetItem *item; std::unique_ptr form; auto title = QString("%1: %2").arg(num_, 2, 16, QChar('0')).toUpper().arg(name_); switch (source_) { case SoundSource::FM: item = new QListWidgetItem(QIcon(":/icon/inst_fm"), title); form = std::make_unique(num_); break; case SoundSource::SSG: item = new QListWidgetItem(QIcon(":/icon/inst_ssg"), title); form = std::make_unique(num_); break; default: return; } // KEEP CODE ORDER // formMan_.lock()->add(num_, std::move(form), name_, source_); item->setSizeHint(QSize(130, 17)); item->setData(Qt::UserRole, num_); list_->insertItem(row_, item); //----------// } void RemoveInstrumentQtCommand::redo() { auto&& item = list_->takeItem(row_); delete item; name_ = formMan_.lock()->getFormInstrumentName(num_); formMan_.lock()->remove(num_); if (QApplication::clipboard()->text().contains( QRegularExpression("^.+_INSTRUMENT:"+QString::number(num_), QRegularExpression::DotMatchesEverythingOption))) { QApplication::clipboard()->clear(); } } int RemoveInstrumentQtCommand::id() const { return CommandId::RemoveInstrument; } BambooTracker-0.3.5/BambooTracker/gui/command/instrument/remove_instrument_qt_command.hpp000066400000000000000000000013731362177441300320730ustar00rootroot00000000000000#ifndef REMOVE_INSTRUMENT_QT_COMMAND_HPP #define REMOVE_INSTRUMENT_QT_COMMAND_HPP #include #include #include #include #include #include "gui/instrument_editor/instrument_form_manager.hpp" #include "misc.hpp" class RemoveInstrumentQtCommand : public QUndoCommand { public: RemoveInstrumentQtCommand(QListWidget *list, int num, int row, std::weak_ptr formMan, QUndoCommand *parent = nullptr); void undo() Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: QListWidget *list_; int num_; QString name_; int row_; SoundSource source_; std::weak_ptr formMan_; }; #endif // REMOVE_INSTRUMENT_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/000077500000000000000000000000001362177441300222725ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/order/clone_order_qt_command.cpp000066400000000000000000000007331362177441300274760ustar00rootroot00000000000000#include "clone_order_qt_command.hpp" #include "command_id.hpp" CloneOrderQtCommand::CloneOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void CloneOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } void CloneOrderQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } int CloneOrderQtCommand::id() const { return CommandId::CloneOrder; } BambooTracker-0.3.5/BambooTracker/gui/command/order/clone_order_qt_command.hpp000066400000000000000000000007061362177441300275030ustar00rootroot00000000000000#ifndef CLONE_ORDER_QT_COMMAND_HPP #define CLONE_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class CloneOrderQtCommand : public QUndoCommand { public: CloneOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // CLONE_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/clone_patterns_qt_command.cpp000066400000000000000000000007501362177441300302220ustar00rootroot00000000000000#include "clone_patterns_qt_command.hpp" #include "command_id.hpp" ClonePatternsQtCommand::ClonePatternsQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void ClonePatternsQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } void ClonePatternsQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } int ClonePatternsQtCommand::id() const { return CommandId::ClonePatterns; } BambooTracker-0.3.5/BambooTracker/gui/command/order/clone_patterns_qt_command.hpp000066400000000000000000000007251362177441300302310ustar00rootroot00000000000000#ifndef CLONE_PATTERNS_QT_COMMAND_HPP #define CLONE_PATTERNS_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class ClonePatternsQtCommand : public QUndoCommand { public: ClonePatternsQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // CLONE_PATTERNS_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/delete_order_qt_command.cpp000066400000000000000000000007441362177441300276420ustar00rootroot00000000000000#include "delete_order_qt_command.hpp" #include "command_id.hpp" DeleteOrderQtCommand::DeleteOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void DeleteOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } void DeleteOrderQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } int DeleteOrderQtCommand::id() const { return CommandId::DeleteOrder; } BambooTracker-0.3.5/BambooTracker/gui/command/order/delete_order_qt_command.hpp000066400000000000000000000007131362177441300276430ustar00rootroot00000000000000#ifndef DELETE_ORDER_QT_COMMAND_HPP #define DELETE_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class DeleteOrderQtCommand : public QUndoCommand { public: DeleteOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // DELETE_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/duplicate_order_qt_command.cpp000066400000000000000000000007671362177441300303570ustar00rootroot00000000000000#include "duplicate_order_qt_command.hpp" #include "command_id.hpp" DuplicateOrderQtCommand::DuplicateOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void DuplicateOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } void DuplicateOrderQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } int DuplicateOrderQtCommand::id() const { return CommandId::DuplicateOrder; } BambooTracker-0.3.5/BambooTracker/gui/command/order/duplicate_order_qt_command.hpp000066400000000000000000000007321362177441300303540ustar00rootroot00000000000000#ifndef DUPLICATE_ORDER_QT_COMMAND_HPP #define DUPLICATE_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class DuplicateOrderQtCommand : public QUndoCommand { public: DuplicateOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // DUPLICATE_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/insert_order_below_qt_command.cpp000066400000000000000000000010061362177441300310640ustar00rootroot00000000000000#include "insert_order_below_qt_command.hpp" #include "command_id.hpp" InsertOrderBelowQtCommand::InsertOrderBelowQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void InsertOrderBelowQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } void InsertOrderBelowQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(true); } int InsertOrderBelowQtCommand::id() const { return CommandId::InsertOrderBelow; } BambooTracker-0.3.5/BambooTracker/gui/command/order/insert_order_below_qt_command.hpp000066400000000000000000000007471362177441300311040ustar00rootroot00000000000000#ifndef INSERT_ORDER_BELOW_QT_COMMAND_HPP #define INSERT_ORDER_BELOW_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class InsertOrderBelowQtCommand : public QUndoCommand { public: InsertOrderBelowQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // INSERT_ORDER_BELOW_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/move_order_qt_command.cpp000066400000000000000000000007141362177441300273430ustar00rootroot00000000000000#include "move_order_qt_command.hpp" #include "command_id.hpp" MoveOrderQtCommand::MoveOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void MoveOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } void MoveOrderQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } int MoveOrderQtCommand::id() const { return CommandId::MoveOrder; } BambooTracker-0.3.5/BambooTracker/gui/command/order/move_order_qt_command.hpp000066400000000000000000000007021362177441300273450ustar00rootroot00000000000000#ifndef MOVE_ORDER_QT_COMMAND_HPP #define MOVE_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class MoveOrderQtCommand : public QUndoCommand { public: MoveOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // MOVE_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/order_commands.hpp000066400000000000000000000006531362177441300260030ustar00rootroot00000000000000#ifndef ORDER_COMMANDS_HPP #define ORDER_COMMANDS_HPP #include "set_pattern_to_order_qt_command.hpp" #include "insert_order_below_qt_command.hpp" #include "delete_order_qt_command.hpp" #include "paste_copied_data_to_order_qt_command.hpp" #include "duplicate_order_qt_command.hpp" #include "move_order_qt_command.hpp" #include "clone_patterns_qt_command.hpp" #include "clone_order_qt_command.hpp" #endif // ORDER_COMMANDS_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/paste_copied_data_to_order_qt_command.cpp000066400000000000000000000010541362177441300325250ustar00rootroot00000000000000#include "paste_copied_data_to_order_qt_command.hpp" #include "command_id.hpp" PasteCopiedDataToOrderQtCommand::PasteCopiedDataToOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void PasteCopiedDataToOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } void PasteCopiedDataToOrderQtCommand::undo() { panel_->redrawByPatternChanged(); panel_->onOrderEdited(); } int PasteCopiedDataToOrderQtCommand::id() const { return CommandId::PasteCopiedDataToOrder; } BambooTracker-0.3.5/BambooTracker/gui/command/order/paste_copied_data_to_order_qt_command.hpp000066400000000000000000000010131362177441300325250ustar00rootroot00000000000000#ifndef PASTE_COPIED_DATA_TO_ORDER_QT_COMMAND_HPP #define PASTE_COPIED_DATA_TO_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" class PasteCopiedDataToOrderQtCommand : public QUndoCommand { public: PasteCopiedDataToOrderQtCommand(OrderListPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: OrderListPanel* panel_; }; #endif // PASTE_COPIED_DATA_TO_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/order/set_pattern_to_order_qt_command.cpp000066400000000000000000000021261362177441300314260ustar00rootroot00000000000000#include "set_pattern_to_order_qt_command.hpp" #include "command_id.hpp" SetPatternToOrderQtCommand::SetPatternToOrderQtCommand(OrderListPanel* panel, OrderPosition pos, bool secondEntry, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), pos_(pos), isSecond_(secondEntry) { } void SetPatternToOrderQtCommand::redo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); } void SetPatternToOrderQtCommand::undo() { panel_->onOrderEdited(); panel_->redrawByPatternChanged(); panel_->resetEntryCount(); } int SetPatternToOrderQtCommand::id() const { return CommandId::SetPatternToOrder; } bool SetPatternToOrderQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecond_) { auto com = dynamic_cast(other); if (com->getPos() == pos_ && com->isSecondEntry()) { isSecond_ = true; redo(); return true; } } isSecond_ = true; return false; } OrderPosition SetPatternToOrderQtCommand::getPos() const { return pos_; } bool SetPatternToOrderQtCommand::isSecondEntry() const { return isSecond_; } BambooTracker-0.3.5/BambooTracker/gui/command/order/set_pattern_to_order_qt_command.hpp000066400000000000000000000013471362177441300314370ustar00rootroot00000000000000#ifndef SET_PATTERN_TO_ORDER_QT_COMMAND_HPP #define SET_PATTERN_TO_ORDER_QT_COMMAND_HPP #include #include "gui/order_list_editor/order_list_panel.hpp" #include "gui/order_list_editor/order_position.hpp" class SetPatternToOrderQtCommand : public QUndoCommand { public: SetPatternToOrderQtCommand(OrderListPanel* panel, OrderPosition pos, bool secondEntry, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; bool mergeWith(const QUndoCommand* other) Q_DECL_OVERRIDE; OrderPosition getPos() const; bool isSecondEntry() const; private: OrderListPanel* panel_; OrderPosition pos_; bool isSecond_; }; #endif // SET_PATTERN_TO_ORDER_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/000077500000000000000000000000001362177441300226345ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/pattern/decrease_note_key_in_pattern_qt_command.cpp000066400000000000000000000010101362177441300334250ustar00rootroot00000000000000#include "decrease_note_key_in_pattern_qt_command.hpp" #include "command_id.hpp" DecreaseNoteKeyInPatternQtCommand::DecreaseNoteKeyInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void DecreaseNoteKeyInPatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void DecreaseNoteKeyInPatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int DecreaseNoteKeyInPatternQtCommand::id() const { return CommandId::DecreaseNoteKeyInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/decrease_note_key_in_pattern_qt_command.hpp000066400000000000000000000010361362177441300334420ustar00rootroot00000000000000#ifndef DECREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP #define DECREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class DecreaseNoteKeyInPatternQtCommand : public QUndoCommand { public: DecreaseNoteKeyInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // DECREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/decrease_note_octave_in_pattern_qt_command.cpp000066400000000000000000000010351362177441300341250ustar00rootroot00000000000000#include "decrease_note_octave_in_pattern_qt_command.hpp" #include "command_id.hpp" DecreaseNoteOctaveInPatternQtCommand::DecreaseNoteOctaveInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void DecreaseNoteOctaveInPatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void DecreaseNoteOctaveInPatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int DecreaseNoteOctaveInPatternQtCommand::id() const { return CommandId::DecreaseNoteOctaveInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/decrease_note_octave_in_pattern_qt_command.hpp000066400000000000000000000010551362177441300341340ustar00rootroot00000000000000#ifndef DECREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP #define DECREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class DecreaseNoteOctaveInPatternQtCommand : public QUndoCommand { public: DecreaseNoteOctaveInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // DECREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/delete_previous_step_qt_command.cpp000066400000000000000000000007441362177441300320000ustar00rootroot00000000000000#include "delete_previous_step_qt_command.hpp" #include "command_id.hpp" DeletePreviousStepQtCommand::DeletePreviousStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void DeletePreviousStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void DeletePreviousStepQtCommand::undo() { panel_->redrawByPatternChanged(true); } int DeletePreviousStepQtCommand::id() const { return CommandId::DeletePreviousStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/delete_previous_step_qt_command.hpp000066400000000000000000000007721362177441300320060ustar00rootroot00000000000000#ifndef DELETE_PREVIOUS_STEP_QT_COMMAND_HPP #define DELETE_PREVIOUS_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class DeletePreviousStepQtCommand : public QUndoCommand { public: DeletePreviousStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // DELETE_PREVIOUS_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_cells_in_pattern_qt_command.cpp000066400000000000000000000007541362177441300322540ustar00rootroot00000000000000#include "erase_cells_in_pattern_qt_command.hpp" #include "command_id.hpp" EraseCellsInPatternQtCommand::EraseCellsInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseCellsInPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void EraseCellsInPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int EraseCellsInPatternQtCommand::id() const { return CommandId::EraseCellsInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_cells_in_pattern_qt_command.hpp000066400000000000000000000010021362177441300322440ustar00rootroot00000000000000#ifndef ERASE_CELLS_IN_PATTERN_QT_COMMAND_HPP #define ERASE_CELLS_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseCellsInPatternQtCommand : public QUndoCommand { public: EraseCellsInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_CELLS_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_effect_in_step_qt_command.cpp000066400000000000000000000007361362177441300317040ustar00rootroot00000000000000#include "erase_effect_in_step_qt_command.hpp" #include "command_id.hpp" EraseEffectInStepQtCommand::EraseEffectInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseEffectInStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void EraseEffectInStepQtCommand::undo() { panel_->redrawByPatternChanged(true); } int EraseEffectInStepQtCommand::id() const { return CommandId::EraseEffectInStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_effect_in_step_qt_command.hpp000066400000000000000000000007701362177441300317070ustar00rootroot00000000000000#ifndef ERASE_EFFECT_IN_STEP_QT_COMMAND_HPP #define ERASE_EFFECT_IN_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseEffectInStepQtCommand : public QUndoCommand { public: EraseEffectInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_EFFECT_IN_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_effect_value_in_step_qt_command.cpp000066400000000000000000000010021362177441300330630ustar00rootroot00000000000000#include "erase_effect_value_in_step_qt_command.hpp" #include "command_id.hpp" EraseEffectValueInStepQtCommand::EraseEffectValueInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseEffectValueInStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void EraseEffectValueInStepQtCommand::undo() { panel_->redrawByPatternChanged(true); } int EraseEffectValueInStepQtCommand::id() const { return CommandId::EraseEffectValueInStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_effect_value_in_step_qt_command.hpp000066400000000000000000000010241362177441300330740ustar00rootroot00000000000000#ifndef ERASE_EFFECT_VALUE_IN_STEP_QT_COMMAND_HPP #define ERASE_EFFECT_VALUE_IN_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseEffectValueInStepQtCommand : public QUndoCommand { public: EraseEffectValueInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_EFFECT_VALUE_IN_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_instrument_in_step_qt_command.cpp000066400000000000000000000007621362177441300326570ustar00rootroot00000000000000#include "erase_instrument_in_step_qt_command.hpp" #include "command_id.hpp" EraseInstrumentInStepQtCommand::EraseInstrumentInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseInstrumentInStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void EraseInstrumentInStepQtCommand::undo() { panel_->redrawByPatternChanged(); } int EraseInstrumentInStepQtCommand::id() const { return CommandId::EraseInstrumentInStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_instrument_in_step_qt_command.hpp000066400000000000000000000010141362177441300326530ustar00rootroot00000000000000#ifndef ERASE_INSTRUMENT_IN_STEP_QT_COMMAND_HPP #define ERASE_INSTRUMENT_IN_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseInstrumentInStepQtCommand : public QUndoCommand { public: EraseInstrumentInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_INSTRUMENT_IN_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_step_qt_command.cpp000066400000000000000000000006441362177441300277000ustar00rootroot00000000000000#include "erase_step_qt_command.hpp" #include "command_id.hpp" EraseStepQtCommand::EraseStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void EraseStepQtCommand::undo() { panel_->redrawByPatternChanged(true); } int EraseStepQtCommand::id() const { return CommandId::EraseStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_step_qt_command.hpp000066400000000000000000000007121362177441300277010ustar00rootroot00000000000000#ifndef ERASE_STEP_QT_COMMAND_HPP #define ERASE_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseStepQtCommand : public QUndoCommand { public: EraseStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_volume_in_step_qt_command.cpp000066400000000000000000000007261362177441300317560ustar00rootroot00000000000000#include "erase_volume_in_step_qt_command.hpp" #include "command_id.hpp" EraseVolumeInStepQtCommand::EraseVolumeInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void EraseVolumeInStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void EraseVolumeInStepQtCommand::undo() { panel_->redrawByPatternChanged(); } int EraseVolumeInStepQtCommand::id() const { return CommandId::EraseVolumeInStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/erase_volume_in_step_qt_command.hpp000066400000000000000000000007701362177441300317620ustar00rootroot00000000000000#ifndef ERASE_VOLUME_IN_STEP_QT_COMMAND_HPP #define ERASE_VOLUME_IN_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class EraseVolumeInStepQtCommand : public QUndoCommand { public: EraseVolumeInStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // ERASE_VOLUME_IN_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/expand_pattern_qt_command.cpp000066400000000000000000000007001362177441300305530ustar00rootroot00000000000000#include "expand_pattern_qt_command.hpp" #include "command_id.hpp" ExpandPatternQtCommand::ExpandPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void ExpandPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void ExpandPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int ExpandPatternQtCommand::id() const { return CommandId::ExpandPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/expand_pattern_qt_command.hpp000066400000000000000000000007361362177441300305710ustar00rootroot00000000000000#ifndef EXPAND_PATTERN_QT_COMMAND_HPP #define EXPAND_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class ExpandPatternQtCommand : public QUndoCommand { public: ExpandPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // EXPAND_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/increase_note_key_in_pattern_qt_command.cpp000066400000000000000000000010101362177441300334430ustar00rootroot00000000000000#include "increase_note_key_in_pattern_qt_command.hpp" #include "command_id.hpp" IncreaseNoteKeyInPatternQtCommand::IncreaseNoteKeyInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void IncreaseNoteKeyInPatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void IncreaseNoteKeyInPatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int IncreaseNoteKeyInPatternQtCommand::id() const { return CommandId::IncreaseNoteKeyInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/increase_note_key_in_pattern_qt_command.hpp000066400000000000000000000010361362177441300334600ustar00rootroot00000000000000#ifndef INCREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP #define INCREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class IncreaseNoteKeyInPatternQtCommand : public QUndoCommand { public: IncreaseNoteKeyInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // INCREASE_NOTE_KEY_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/increase_note_octave_in_pattern_qt_command.cpp000066400000000000000000000010351362177441300341430ustar00rootroot00000000000000#include "increase_note_octave_in_pattern_qt_command.hpp" #include "command_id.hpp" IncreaseNoteOctaveInPatternQtCommand::IncreaseNoteOctaveInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void IncreaseNoteOctaveInPatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void IncreaseNoteOctaveInPatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int IncreaseNoteOctaveInPatternQtCommand::id() const { return CommandId::IncreaseNoteOctaveInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/increase_note_octave_in_pattern_qt_command.hpp000066400000000000000000000010551362177441300341520ustar00rootroot00000000000000#ifndef INCREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP #define INCREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class IncreaseNoteOctaveInPatternQtCommand : public QUndoCommand { public: IncreaseNoteOctaveInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // INCREASE_NOTE_OCTAVE_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/insert_step_qt_command.cpp000066400000000000000000000006531362177441300301050ustar00rootroot00000000000000#include "insert_step_qt_command.hpp" #include "command_id.hpp" InsertStepQtCommand::InsertStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void InsertStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void InsertStepQtCommand::undo() { panel_->redrawByPatternChanged(true); } int InsertStepQtCommand::id() const { return CommandId::InsertStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/insert_step_qt_command.hpp000066400000000000000000000007171362177441300301130ustar00rootroot00000000000000#ifndef INSERT_STEP_QT_COMMAND_HPP #define INSERT_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class InsertStepQtCommand : public QUndoCommand { public: InsertStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // INSERT_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/interpolate_pattern_qt_command.cpp000066400000000000000000000007331362177441300316300ustar00rootroot00000000000000#include "interpolate_pattern_qt_command.hpp" #include "command_id.hpp" InterpolatePatternQtCommand::InterpolatePatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void InterpolatePatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void InterpolatePatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int InterpolatePatternQtCommand::id() const { return CommandId::InterpolatePattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/interpolate_pattern_qt_command.hpp000066400000000000000000000007671362177441300316440ustar00rootroot00000000000000#ifndef INTERPOLATE_PATTERN_QT_COMMAND_HPP #define INTERPOLATE_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class InterpolatePatternQtCommand : public QUndoCommand { public: InterpolatePatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // INTERPOLATE_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/paste_copied_data_to_pattern_qt_command.cpp000066400000000000000000000010201362177441300334220ustar00rootroot00000000000000#include "paste_copied_data_to_pattern_qt_command.hpp" #include "command_id.hpp" PasteCopiedDataToPatternQtCommand::PasteCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void PasteCopiedDataToPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void PasteCopiedDataToPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int PasteCopiedDataToPatternQtCommand::id() const { return CommandId::PasteCopiedDataToPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/paste_copied_data_to_pattern_qt_command.hpp000066400000000000000000000010361362177441300334360ustar00rootroot00000000000000#ifndef PASTE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #define PASTE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class PasteCopiedDataToPatternQtCommand : public QUndoCommand { public: PasteCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // PASTE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP paste_mix_copied_data_to_pattern_qt_command.cpp000066400000000000000000000010461362177441300342300ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/pattern#include "paste_mix_copied_data_to_pattern_qt_command.hpp" #include "command_id.hpp" PasteMixCopiedDataToPatternQtCommand::PasteMixCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void PasteMixCopiedDataToPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void PasteMixCopiedDataToPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int PasteMixCopiedDataToPatternQtCommand::id() const { return CommandId::PasteMixCopiedDataToPattern; } paste_mix_copied_data_to_pattern_qt_command.hpp000066400000000000000000000010601362177441300342310ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/pattern#ifndef PASTE_MIX_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #define PASTE_MIX_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class PasteMixCopiedDataToPatternQtCommand : public QUndoCommand { public: PasteMixCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // PASTE_MIX_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP paste_overwrite_copied_data_to_pattern_qt_command.cpp000066400000000000000000000011201362177441300354520ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/pattern#include "paste_overwrite_copied_data_to_pattern_qt_command.hpp" #include "command_id.hpp" PasteOverwriteCopiedDataToPatternQtCommand::PasteOverwriteCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void PasteOverwriteCopiedDataToPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void PasteOverwriteCopiedDataToPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int PasteOverwriteCopiedDataToPatternQtCommand::id() const { return CommandId::PasteOverwriteCopiedDataToPattern; } paste_overwrite_copied_data_to_pattern_qt_command.hpp000066400000000000000000000011161362177441300354640ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/command/pattern#ifndef PASTE_OVERWRITE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #define PASTE_OVERWRITE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class PasteOverwriteCopiedDataToPatternQtCommand : public QUndoCommand { public: PasteOverwriteCopiedDataToPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // PASTE_OVERWRITE_COPIED_DATA_TO_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/pattern_commands_qt.hpp000066400000000000000000000026761362177441300274220ustar00rootroot00000000000000#ifndef PATTERN_COMMANDS_QT_HPP #define PATTERN_COMMANDS_QT_HPP /********** Pattern edit **********/ #include "set_key_on_to_step_qt_command.hpp" #include "set_key_off_to_step_qt_command.hpp" #include "erase_step_qt_command.hpp" #include "set_instrument_to_step_qt_command.hpp" #include "erase_instrument_in_step_qt_command.hpp" #include "set_volume_to_step_qt_command.hpp" #include "erase_volume_in_step_qt_command.hpp" #include "set_effect_id_to_step_qt_command.hpp" #include "erase_effect_in_step_qt_command.hpp" #include "set_effect_value_to_step_qt_command.hpp" #include "erase_effect_value_in_step_qt_command.hpp" #include "insert_step_qt_command.hpp" #include "delete_previous_step_qt_command.hpp" #include "paste_copied_data_to_pattern_qt_command.hpp" #include "erase_cells_in_pattern_qt_command.hpp" #include "paste_mix_copied_data_to_pattern_qt_command.hpp" #include "increase_note_key_in_pattern_qt_command.hpp" #include "decrease_note_key_in_pattern_qt_command.hpp" #include "increase_note_octave_in_pattern_qt_command.hpp" #include "decrease_note_octave_in_pattern_qt_command.hpp" #include "expand_pattern_qt_command.hpp" #include "shrink_pattern_qt_command.hpp" #include "set_echo_buffer_access_qt_command.hpp" #include "interpolate_pattern_qt_command.hpp" #include "reverse_pattern_qt_command.hpp" #include "replace_instrument_in_pattern_qt_command.hpp" #include "paste_overwrite_copied_data_to_pattern_qt_command.hpp" #endif // PATTERN_COMMANDS_QT_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/replace_instrument_in_pattern_qt_command.cpp000066400000000000000000000010251362177441300336660ustar00rootroot00000000000000#include "replace_instrument_in_pattern_qt_command.hpp" #include "command_id.hpp" ReplaceInstrumentInPatternQtCommand::ReplaceInstrumentInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void ReplaceInstrumentInPatternQtCommand::redo() { panel_->redrawByPatternChanged(); } void ReplaceInstrumentInPatternQtCommand::undo() { panel_->redrawByPatternChanged(); } int ReplaceInstrumentInPatternQtCommand::id() const { return CommandId::ReplaceInstrumentInPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/replace_instrument_in_pattern_qt_command.hpp000066400000000000000000000010451362177441300336750ustar00rootroot00000000000000#ifndef REPLACE_INSTRUMENT_IN_PATTERN_QT_COMMAND_HPP #define REPLACE_INSTRUMENT_IN_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class ReplaceInstrumentInPatternQtCommand : public QUndoCommand { public: ReplaceInstrumentInPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // REPLACE_INSTRUMENT_IN_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/reverse_pattern_qt_command.cpp000066400000000000000000000007071362177441300307560ustar00rootroot00000000000000#include "reverse_pattern_qt_command.hpp" #include "command_id.hpp" ReversePatternQtCommand::ReversePatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void ReversePatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void ReversePatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int ReversePatternQtCommand::id() const { return CommandId::ReversePattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/reverse_pattern_qt_command.hpp000066400000000000000000000007431362177441300307630ustar00rootroot00000000000000#ifndef REVERSE_PATTERN_QT_COMMAND_HPP #define REVERSE_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class ReversePatternQtCommand : public QUndoCommand { public: ReversePatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // REVERSE_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_echo_buffer_access_qt_command.cpp000066400000000000000000000007441362177441300322120ustar00rootroot00000000000000#include "set_echo_buffer_access_qt_command.hpp" #include "command_id.hpp" SetEchoBufferAccessQtCommand::SetEchoBufferAccessQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void SetEchoBufferAccessQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetEchoBufferAccessQtCommand::undo() { panel_->redrawByPatternChanged(); } int SetEchoBufferAccessQtCommand::id() const { return CommandId::SetEchoBufferAccess; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_echo_buffer_access_qt_command.hpp000066400000000000000000000010031362177441300322040ustar00rootroot00000000000000#ifndef SET_ECHO_BUFFER_ACCESS_QT_COMMAND_HPP #define SET_ECHO_BUFFER_ACCESS_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class SetEchoBufferAccessQtCommand : public QUndoCommand { public: SetEchoBufferAccessQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // SET_ECHO_BUFFER_ACCESS_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_effect_id_to_step_qt_command.cpp000066400000000000000000000020631362177441300320630ustar00rootroot00000000000000#include "set_effect_id_to_step_qt_command.hpp" #include "command_id.hpp" SetEffectIDToStepQtCommand::SetEffectIDToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), pos_(pos), isSecond_(secondEntry) { } void SetEffectIDToStepQtCommand::redo() { panel_->redrawByPatternChanged(true); } void SetEffectIDToStepQtCommand::undo() { panel_->redrawByPatternChanged(true); panel_->resetEntryCount(); } int SetEffectIDToStepQtCommand::id() const { return CommandId::SetEffectIDToStep; } bool SetEffectIDToStepQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecond_) { auto com = dynamic_cast(other); if (com->getPos() == pos_ && com->isSecondEntry()) { isSecond_ = true; redo(); return true; } } isSecond_ = true; return false; } PatternPosition SetEffectIDToStepQtCommand::getPos() const { return pos_; } bool SetEffectIDToStepQtCommand::isSecondEntry() const { return isSecond_; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_effect_id_to_step_qt_command.hpp000066400000000000000000000013701362177441300320700ustar00rootroot00000000000000#ifndef SET_EFFECT_ID_TO_STEP_QT_COMMAND_HPP #define SET_EFFECT_ID_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" #include "gui/pattern_editor/pattern_position.hpp" class SetEffectIDToStepQtCommand : public QUndoCommand { public: SetEffectIDToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; bool mergeWith(const QUndoCommand* other) Q_DECL_OVERRIDE; PatternPosition getPos() const; bool isSecondEntry() const; private: PatternEditorPanel* panel_; PatternPosition pos_; bool isSecond_; }; #endif // SET_EFFECT_ID_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_effect_value_to_step_qt_command.cpp000066400000000000000000000021141362177441300326000ustar00rootroot00000000000000#include "set_effect_value_to_step_qt_command.hpp" #include "command_id.hpp" SetEffectValueToStepQtCommand::SetEffectValueToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), pos_(pos), isSecond_(secondEntry) { } void SetEffectValueToStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetEffectValueToStepQtCommand::undo() { panel_->redrawByPatternChanged(); panel_->resetEntryCount(); } int SetEffectValueToStepQtCommand::id() const { return CommandId::SetEffectValueToStep; } bool SetEffectValueToStepQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecond_) { auto com = dynamic_cast(other); if (com->getPos() == pos_ && com->isSecondEntry()) { isSecond_ = true; redo(); return true; } } isSecond_ = true; return false; } PatternPosition SetEffectValueToStepQtCommand::getPos() const { return pos_; } bool SetEffectValueToStepQtCommand::isSecondEntry() const { return isSecond_; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_effect_value_to_step_qt_command.hpp000066400000000000000000000014071362177441300326110ustar00rootroot00000000000000#ifndef SET_EFFECT_VALUE_TO_STEP_QT_COMMAND_HPP #define SET_EFFECT_VALUE_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" #include "gui/pattern_editor/pattern_position.hpp" class SetEffectValueToStepQtCommand : public QUndoCommand { public: SetEffectValueToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; bool mergeWith(const QUndoCommand* other) Q_DECL_OVERRIDE; PatternPosition getPos() const; bool isSecondEntry() const; private: PatternEditorPanel* panel_; PatternPosition pos_; bool isSecond_; }; #endif // SET_EFFECT_VALUE_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_instrument_to_step_qt_command.cpp000066400000000000000000000021001362177441300323530ustar00rootroot00000000000000#include "set_instrument_to_step_qt_command.hpp" #include "command_id.hpp" SetInstrumentToStepQtCommand::SetInstrumentToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), pos_(pos), isSecond_(secondEntry) { } void SetInstrumentToStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetInstrumentToStepQtCommand::undo() { panel_->redrawByPatternChanged(); panel_->resetEntryCount(); } int SetInstrumentToStepQtCommand::id() const { return CommandId::SetInstrumentInStep; } bool SetInstrumentToStepQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecond_) { auto com = dynamic_cast(other); if (com->getPos() == pos_ && com->isSecondEntry()) { isSecond_ = true; redo(); return true; } } isSecond_ = true; return false; } PatternPosition SetInstrumentToStepQtCommand::getPos() const { return pos_; } bool SetInstrumentToStepQtCommand::isSecondEntry() const { return isSecond_; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_instrument_to_step_qt_command.hpp000066400000000000000000000013771362177441300323770ustar00rootroot00000000000000#ifndef SET_INSTRUMENT_TO_STEP_QT_COMMAND_HPP #define SET_INSTRUMENT_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" #include "gui/pattern_editor/pattern_position.hpp" class SetInstrumentToStepQtCommand : public QUndoCommand { public: SetInstrumentToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; bool mergeWith(const QUndoCommand* other) Q_DECL_OVERRIDE; PatternPosition getPos() const; bool isSecondEntry() const; private: PatternEditorPanel* panel_; PatternPosition pos_; bool isSecond_; }; #endif // SET_INSTRUMENT_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_key_off_to_step_qt_command.cpp000066400000000000000000000007111362177441300315730ustar00rootroot00000000000000#include "set_key_off_to_step_qt_command.hpp" #include "command_id.hpp" SetKeyOffToStepQtCommand::SetKeyOffToStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void SetKeyOffToStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetKeyOffToStepQtCommand::undo() { panel_->redrawByPatternChanged(); } int SetKeyOffToStepQtCommand::id() const { return CommandId::SetKeyOffToStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_key_off_to_step_qt_command.hpp000066400000000000000000000007611362177441300316050ustar00rootroot00000000000000#ifndef SET_KEY_OFF_TO_STEP_QT_COMMAND_HPP #define SET_KEY_OFF_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class SetKeyOffToStepQtCommand : public QUndoCommand { public: SetKeyOffToStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // SET_KEY_OFF_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_key_on_to_step_qt_command.cpp000066400000000000000000000007021362177441300314350ustar00rootroot00000000000000#include "set_key_on_to_step_qt_command.hpp" #include "command_id.hpp" SetKeyOnToStepQtCommand::SetKeyOnToStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void SetKeyOnToStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetKeyOnToStepQtCommand::undo() { panel_->redrawByPatternChanged(); } int SetKeyOnToStepQtCommand::id() const { return CommandId::SetKeyOnToStep; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_key_on_to_step_qt_command.hpp000066400000000000000000000007541362177441300314510ustar00rootroot00000000000000#ifndef SET_KEY_ON_TO_STEP_QT_COMMAND_HPP #define SET_KEY_ON_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class SetKeyOnToStepQtCommand : public QUndoCommand { public: SetKeyOnToStepQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // SET_KEY_ON_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_volume_to_step_qt_command.cpp000066400000000000000000000020241362177441300314570ustar00rootroot00000000000000#include "set_volume_to_step_qt_command.hpp" #include "command_id.hpp" SetVolumeToStepQtCommand::SetVolumeToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel), pos_(pos), isSecond_(secondEntry) { } void SetVolumeToStepQtCommand::redo() { panel_->redrawByPatternChanged(); } void SetVolumeToStepQtCommand::undo() { panel_->redrawByPatternChanged(); panel_->resetEntryCount(); } int SetVolumeToStepQtCommand::id() const { return CommandId::SetVolumeToStep; } bool SetVolumeToStepQtCommand::mergeWith(const QUndoCommand* other) { if (other->id() == id() && !isSecond_) { auto com = dynamic_cast(other); if (com->getPos() == pos_ && com->isSecondEntry()) { isSecond_ = true; redo(); return true; } } isSecond_ = true; return false; } PatternPosition SetVolumeToStepQtCommand::getPos() const { return pos_; } bool SetVolumeToStepQtCommand::isSecondEntry() const { return isSecond_; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/set_volume_to_step_qt_command.hpp000066400000000000000000000013531362177441300314700ustar00rootroot00000000000000#ifndef SET_VOLUME_TO_STEP_QT_COMMAND_HPP #define SET_VOLUME_TO_STEP_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" #include "gui/pattern_editor/pattern_position.hpp" class SetVolumeToStepQtCommand : public QUndoCommand { public: SetVolumeToStepQtCommand(PatternEditorPanel* panel, PatternPosition pos, bool secondEntry, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; bool mergeWith(const QUndoCommand* other) Q_DECL_OVERRIDE; PatternPosition getPos() const; bool isSecondEntry() const; private: PatternEditorPanel* panel_; PatternPosition pos_; bool isSecond_; }; #endif // SET_VOLUME_TO_STEP_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/command/pattern/shrink_pattern_qt_command.cpp000066400000000000000000000007001362177441300305720ustar00rootroot00000000000000#include "shrink_pattern_qt_command.hpp" #include "command_id.hpp" ShrinkPatternQtCommand::ShrinkPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent) : QUndoCommand(parent), panel_(panel) { } void ShrinkPatternQtCommand::redo() { panel_->redrawByPatternChanged(true); } void ShrinkPatternQtCommand::undo() { panel_->redrawByPatternChanged(true); } int ShrinkPatternQtCommand::id() const { return CommandId::ShrinkPattern; } BambooTracker-0.3.5/BambooTracker/gui/command/pattern/shrink_pattern_qt_command.hpp000066400000000000000000000007361362177441300306100ustar00rootroot00000000000000#ifndef SHRINK_PATTERN_QT_COMMAND_HPP #define SHRINK_PATTERN_QT_COMMAND_HPP #include #include "gui/pattern_editor/pattern_editor_panel.hpp" class ShrinkPatternQtCommand : public QUndoCommand { public: ShrinkPatternQtCommand(PatternEditorPanel* panel, QUndoCommand* parent = nullptr); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; int id() const Q_DECL_OVERRIDE; private: PatternEditorPanel* panel_; }; #endif // SHRINK_PATTERN_QT_COMMAND_HPP BambooTracker-0.3.5/BambooTracker/gui/comment_edit_dialog.cpp000066400000000000000000000007351362177441300242400ustar00rootroot00000000000000#include "comment_edit_dialog.hpp" #include "ui_comment_edit_dialog.h" CommentEditDialog::CommentEditDialog(QString comment, QWidget *parent) : QDialog(parent), ui(new Ui::CommentEditDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui->plainTextEdit->setPlainText(comment); } CommentEditDialog::~CommentEditDialog() { delete ui; } QString CommentEditDialog::getComment() const { return ui->plainTextEdit->toPlainText(); } BambooTracker-0.3.5/BambooTracker/gui/comment_edit_dialog.hpp000066400000000000000000000006331362177441300242420ustar00rootroot00000000000000#ifndef COMMENT_EDIT_DIALOG_HPP #define COMMENT_EDIT_DIALOG_HPP #include #include namespace Ui { class CommentEditDialog; } class CommentEditDialog : public QDialog { Q_OBJECT public: explicit CommentEditDialog(QString comment, QWidget *parent = nullptr); ~CommentEditDialog(); QString getComment() const; private: Ui::CommentEditDialog *ui; }; #endif // COMMENT_EDIT_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/comment_edit_dialog.ui000066400000000000000000000027751362177441300241010ustar00rootroot00000000000000 CommentEditDialog 0 0 400 300 Module Comment Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() CommentEditDialog accept() 248 254 157 274 buttonBox rejected() CommentEditDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/configuration_dialog.cpp000066400000000000000000000772411362177441300244460ustar00rootroot00000000000000#include "configuration_dialog.hpp" #include "ui_configuration_dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include "slider_style.hpp" #include "fm_envelope_set_edit_dialog.hpp" #include "midi/midi.hpp" #include "jam_manager.hpp" #include "chips/chip_misc.h" #include "color_palette_handler.hpp" ConfigurationDialog::ConfigurationDialog(std::weak_ptr config, std::weak_ptr palette, std::string curApi, std::vector apis, QWidget *parent) : QDialog(parent), ui(new Ui::ConfigurationDialog), config_(config), palette_(palette) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); QObject::connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, [&] { on_ConfigurationDialog_accepted(); emit applyPressed(); }); std::shared_ptr configLocked = config.lock(); // General // // General settings ui->generalSettingsListWidget->item(0)->setCheckState(toCheckState(configLocked->getWarpCursor())); ui->generalSettingsListWidget->item(1)->setCheckState(toCheckState(configLocked->getWarpAcrossOrders())); ui->generalSettingsListWidget->item(2)->setCheckState(toCheckState(configLocked->getShowRowNumberInHex())); ui->generalSettingsListWidget->item(3)->setCheckState(toCheckState(configLocked->getShowPreviousNextOrders())); ui->generalSettingsListWidget->item(4)->setCheckState(toCheckState(configLocked->getBackupModules())); ui->generalSettingsListWidget->item(5)->setCheckState(toCheckState(configLocked->getDontSelectOnDoubleClick())); ui->generalSettingsListWidget->item(6)->setCheckState(toCheckState(configLocked->getReverseFMVolumeOrder())); ui->generalSettingsListWidget->item(7)->setCheckState(toCheckState(configLocked->getMoveCursorToRight())); ui->generalSettingsListWidget->item(8)->setCheckState(toCheckState(configLocked->getRetrieveChannelState())); ui->generalSettingsListWidget->item(9)->setCheckState(toCheckState(configLocked->getEnableTranslation())); ui->generalSettingsListWidget->item(10)->setCheckState(toCheckState(configLocked->getShowFMDetuneAsSigned())); ui->generalSettingsListWidget->item(11)->setCheckState(toCheckState(configLocked->getShowWaveVisual())); ui->generalSettingsListWidget->item(12)->setCheckState(toCheckState(configLocked->getFill00ToEffectValue())); ui->generalSettingsListWidget->item(13)->setCheckState(toCheckState(configLocked->getAutosetInstrument())); ui->generalSettingsListWidget->item(14)->setCheckState(toCheckState(configLocked->getMoveCursorByHorizontalScroll())); // Edit settings ui->pageJumpLengthSpinBox->setValue(static_cast(configLocked->getPageJumpLength())); // Keys ui->keyOffKeySequenceEdit->setKeySequence( QString::fromUtf8(configLocked->getKeyOffKey().c_str(), static_cast(configLocked->getKeyOffKey().length()))); ui->octaveUpKeySequenceEdit->setKeySequence( QString::fromUtf8(configLocked->getOctaveUpKey().c_str(), static_cast(configLocked->getOctaveUpKey().length()))); ui->octaveDownKeySequenceEdit->setKeySequence( QString::fromUtf8(configLocked->getOctaveDownKey().c_str(), static_cast(configLocked->getOctaveDownKey().length()))); ui->echoBufferKeySequenceEdit->setKeySequence( QString::fromUtf8(configLocked->getEchoBufferKey().c_str(), static_cast(configLocked->getEchoBufferKey().length()))); ui->keyboardTypeComboBox->setCurrentIndex(static_cast(configLocked->getNoteEntryLayout())); customLayoutKeysMap = { { JamKey::LowC, ui->lowCEdit }, { JamKey::LowCS, ui->lowCSEdit }, { JamKey::LowD, ui->lowDEdit }, { JamKey::LowDS, ui->lowDSEdit }, { JamKey::LowE, ui->lowEEdit }, { JamKey::LowF, ui->lowFEdit }, { JamKey::LowFS, ui->lowFSEdit }, { JamKey::LowG, ui->lowGEdit }, { JamKey::LowGS, ui->lowGSEdit }, { JamKey::LowA, ui->lowAEdit }, { JamKey::LowAS, ui->lowASEdit }, { JamKey::LowB, ui->lowBEdit }, { JamKey::LowC2, ui->lowHighCEdit }, { JamKey::LowCS2, ui->lowHighCSEdit }, { JamKey::LowD2, ui->lowHighDEdit }, { JamKey::HighC, ui->highCEdit }, { JamKey::HighCS, ui->highCSEdit }, { JamKey::HighD, ui->highDEdit }, { JamKey::HighDS, ui->highDSEdit }, { JamKey::HighE, ui->highEEdit }, { JamKey::HighF, ui->highFEdit }, { JamKey::HighFS, ui->highFSEdit }, { JamKey::HighG, ui->highGEdit }, { JamKey::HighGS, ui->highGSEdit }, { JamKey::HighA, ui->highAEdit }, { JamKey::HighAS, ui->highASEdit }, { JamKey::HighB, ui->highBEdit }, { JamKey::HighC2, ui->highHighCEdit }, { JamKey::HighCS2, ui->highHighCSEdit }, { JamKey::HighD2, ui->highHighDEdit } }; std::unordered_map customLayoutMapping = configLocked->getCustomLayoutKeys(); std::unordered_map::const_iterator customLayoutMappingIterator = customLayoutMapping.begin(); while (customLayoutMappingIterator != customLayoutMapping.end()) { customLayoutKeysMap.at(customLayoutMappingIterator->second)->setKeySequence(QKeySequence(QString::fromStdString(customLayoutMappingIterator->first))); customLayoutMappingIterator++; } // Emulation // ui->emulatorComboBox->addItem("MAME YM2608", static_cast(chip::Emu::Mame)); ui->emulatorComboBox->addItem("Nuked OPN-Mod", static_cast(chip::Emu::Nuked)); ui->emulatorComboBox->setCurrentIndex(ui->emulatorComboBox->findData(configLocked->getEmulator())); // Sound // int apiRow = -1; int defApiRow = 0; for (auto& name : apis) { ui->soundAPIComboBox->addItem(QString::fromUtf8(name.c_str(), static_cast(name.length()))); if (name == configLocked->getSoundAPI()) apiRow = ui->soundAPIComboBox->count() - 1; if (name == curApi) defApiRow = apiRow = ui->soundAPIComboBox->count() - 1; } ui->soundAPIComboBox->setCurrentIndex((apiRow == -1) ? defApiRow : apiRow); int devRow = -1; int defDevRow = 0; for (auto& info : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { ui->soundDeviceComboBox->addItem(info.deviceName()); if (info.deviceName() == QString::fromUtf8(configLocked->getSoundDevice().c_str(), static_cast(configLocked->getSoundDevice().length()))) devRow = ui->soundDeviceComboBox->count() - 1; if (info.deviceName() == QAudioDeviceInfo::defaultOutputDevice().deviceName()) { defDevRow = ui->soundDeviceComboBox->count() - 1; } } ui->soundDeviceComboBox->setCurrentIndex((devRow == -1) ? defDevRow : devRow); ui->realChipComboBox->addItem(tr("None"), static_cast(RealChipInterface::NONE)); ui->realChipComboBox->addItem("SCCI", static_cast(RealChipInterface::SCCI)); ui->realChipComboBox->addItem("C86CTL", static_cast(RealChipInterface::C86CTL)); switch (configLocked->getRealChipInterface()) { default: // Fall through case RealChipInterface::NONE: ui->realChipComboBox->setCurrentIndex(0); break; case RealChipInterface::SCCI: ui->realChipComboBox->setCurrentIndex(1); break; case RealChipInterface::C86CTL: ui->realChipComboBox->setCurrentIndex(2); break; } ui->sampleRateComboBox->addItem("44100Hz", 44100); ui->sampleRateComboBox->addItem("48000Hz", 48000); ui->sampleRateComboBox->addItem("55466Hz", 55466); switch (configLocked->getSampleRate()) { default: // Fall through case 44100: ui->sampleRateComboBox->setCurrentIndex(0); break; case 48000: ui->sampleRateComboBox->setCurrentIndex(1); break; case 55466: ui->sampleRateComboBox->setCurrentIndex(2); break; } ui->bufferLengthHorizontalSlider->setStyle(new SliderStyle()); QObject::connect(ui->bufferLengthHorizontalSlider, &QSlider::valueChanged, this, [&](int value) { ui->bufferLengthLabel->setText(QString::number(value) + "ms"); }); ui->bufferLengthHorizontalSlider->setValue(static_cast(configLocked->getBufferLength())); // Midi // MidiInterface &midiIntf = MidiInterface::instance(); if (midiIntf.supportsVirtualPort()) ui->midiInputNameLine->setPlaceholderText(tr("Virtual port")); ui->midiInputNameLine->setText(QString::fromStdString(configLocked->getMidiInputPort())); // Mixer // ui->masterMixerSlider->setText(tr("Master")); ui->masterMixerSlider->setSuffix("%"); ui->masterMixerSlider->setMaximum(200); ui->masterMixerSlider->setMinimum(0); ui->masterMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->masterMixerSlider->setTickInterval(20); ui->masterMixerSlider->setValue(configLocked->getMixerVolumeMaster()); ui->fmMixerSlider->setText("FM"); ui->fmMixerSlider->setSuffix("dB"); ui->fmMixerSlider->setMaximum(120); ui->fmMixerSlider->setMinimum(-120); ui->fmMixerSlider->setValueRate(0.1); ui->fmMixerSlider->setSign(true); ui->fmMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->fmMixerSlider->setTickInterval(20); ui->fmMixerSlider->setValue(static_cast(configLocked->getMixerVolumeFM() * 10)); ui->ssgMixerSlider->setText("SSG"); ui->ssgMixerSlider->setSuffix("dB"); ui->ssgMixerSlider->setMaximum(120); ui->ssgMixerSlider->setMinimum(-120); ui->ssgMixerSlider->setValueRate(0.1); ui->ssgMixerSlider->setSign(true); ui->ssgMixerSlider->setTickPosition(QSlider::TicksBothSides); ui->ssgMixerSlider->setTickInterval(20); ui->ssgMixerSlider->setValue(static_cast(configLocked->getMixerVolumeSSG() * 10)); // Formats // fmEnvelopeTexts_ = configLocked->getFMEnvelopeTexts(); updateEnvelopeSetUi(); // Appearance // ui->colorsTreeWidget->setColumnWidth(0, 250); updateColorTree(); ui->ptnHdFontComboBox->setCurrentFont( QFont(QString::fromUtf8(configLocked->getPatternEditorHeaderFont().c_str(), static_cast(configLocked->getPatternEditorHeaderFont().size())))); ui->ptnHdFontSizeComboBox->setCurrentText(QString::number(configLocked->getPatternEditorHeaderFontSize())); ui->ptnRowFontComboBox->setCurrentFont( QFont(QString::fromUtf8(configLocked->getPatternEditorRowsFont().c_str(), static_cast(configLocked->getPatternEditorRowsFont().size())))); ui->ptnRowFontSizeComboBox->setCurrentText(QString::number(configLocked->getPatternEditorRowsFontSize())); ui->odrHdFontComboBox->setCurrentFont( QFont(QString::fromUtf8(configLocked->getOrderListHeaderFont().c_str(), static_cast(configLocked->getOrderListHeaderFont().size())))); ui->odrHdFontSizeComboBox->setCurrentText(QString::number(configLocked->getOrderListHeaderFontSize())); ui->odrRowFontComboBox->setCurrentFont( QFont(QString::fromUtf8(configLocked->getOrderListRowsFont().c_str(), static_cast(configLocked->getOrderListRowsFont().size())))); ui->odrRowFontSizeComboBox->setCurrentText(QString::number(configLocked->getOrderListRowsFontSize())); } ConfigurationDialog::~ConfigurationDialog() { delete ui; } void ConfigurationDialog::on_ConfigurationDialog_accepted() { std::shared_ptr configLocked = config_.lock(); // General // // General settings configLocked->setWarpCursor(fromCheckState(ui->generalSettingsListWidget->item(0)->checkState())); configLocked->setWarpAcrossOrders(fromCheckState(ui->generalSettingsListWidget->item(1)->checkState())); configLocked->setShowRowNumberInHex(fromCheckState(ui->generalSettingsListWidget->item(2)->checkState())); configLocked->setShowPreviousNextOrders(fromCheckState(ui->generalSettingsListWidget->item(3)->checkState())); configLocked->setBackupModules(fromCheckState(ui->generalSettingsListWidget->item(4)->checkState())); configLocked->setDontSelectOnDoubleClick(fromCheckState(ui->generalSettingsListWidget->item(5)->checkState())); configLocked->setReverseFMVolumeOrder(fromCheckState(ui->generalSettingsListWidget->item(6)->checkState())); configLocked->setMoveCursorToRight(fromCheckState(ui->generalSettingsListWidget->item(7)->checkState())); configLocked->setRetrieveChannelState(fromCheckState(ui->generalSettingsListWidget->item(8)->checkState())); configLocked->setEnableTranslation(fromCheckState(ui->generalSettingsListWidget->item(9)->checkState())); configLocked->setShowFMDetuneAsSigned(fromCheckState(ui->generalSettingsListWidget->item(10)->checkState())); configLocked->setShowWaveVisual(fromCheckState(ui->generalSettingsListWidget->item(11)->checkState())); configLocked->setFill00ToEffectValue(fromCheckState(ui->generalSettingsListWidget->item(12)->checkState())); configLocked->setAutosetInstrument(fromCheckState(ui->generalSettingsListWidget->item(13)->checkState())); configLocked->setMoveCursorByHorizontalScroll(fromCheckState(ui->generalSettingsListWidget->item(14)->checkState())); // Edit settings configLocked->setPageJumpLength(static_cast(ui->pageJumpLengthSpinBox->value())); // Keys configLocked->setKeyOffKey(ui->keyOffKeySequenceEdit->keySequence().toString().toStdString()); configLocked->setOctaveUpKey(ui->octaveUpKeySequenceEdit->keySequence().toString().toStdString()); configLocked->setOctaveDownKey(ui->octaveDownKeySequenceEdit->keySequence().toString().toStdString()); configLocked->setEchoBufferKey(ui->echoBufferKeySequenceEdit->keySequence().toString().toStdString()); configLocked->setNoteEntryLayout(static_cast(ui->keyboardTypeComboBox->currentIndex())); std::unordered_map customLayoutNewKeys; std::unordered_map::const_iterator customLayoutKeysMapIterator = customLayoutKeysMap.begin(); while (customLayoutKeysMapIterator != customLayoutKeysMap.end()) { customLayoutNewKeys[customLayoutKeysMapIterator->second->keySequence().toString().toStdString()] = customLayoutKeysMapIterator->first; customLayoutKeysMapIterator++; } configLocked->setCustomLayoutKeys(customLayoutNewKeys); // Emulation // int emu = ui->emulatorComboBox->currentData().toInt(); bool changedEmu = false; if (emu != configLocked->getEmulator()) { configLocked->setEmulator(emu); changedEmu = true; } // Sound // configLocked->setSoundDevice(ui->soundDeviceComboBox->currentText().toUtf8().toStdString()); configLocked->setSoundAPI(ui->soundAPIComboBox->currentText().toUtf8().toStdString()); configLocked->setRealChipInterface(static_cast( ui->realChipComboBox->currentData(Qt::UserRole).toInt())); configLocked->setSampleRate(ui->sampleRateComboBox->currentData(Qt::UserRole).toUInt()); configLocked->setBufferLength(static_cast(ui->bufferLengthHorizontalSlider->value())); // Midi // configLocked->setMidiInputPort(ui->midiInputNameLine->text().toStdString()); // Mixer // configLocked->setMixerVolumeMaster(ui->masterMixerSlider->value()); configLocked->setMixerVolumeFM(ui->fmMixerSlider->value() * 0.1); configLocked->setMixerVolumeSSG(ui->ssgMixerSlider->value() * 0.1); // Formats // std::sort(fmEnvelopeTexts_.begin(), fmEnvelopeTexts_.end(), [](const FMEnvelopeText& a, const FMEnvelopeText& b) -> bool { return (a.name < b.name); }); configLocked->setFMEnvelopeTexts(fmEnvelopeTexts_); // Appearance // auto* pl = palette_.lock().get(); QTreeWidgetItem* ptnColors = ui->colorsTreeWidget->topLevelItem(0); pl->ptnDefTextColor = ptnColors->child(0)->data(1, Qt::BackgroundRole).value(); pl->ptnDefStepColor = ptnColors->child(1)->data(1, Qt::BackgroundRole).value(); pl->ptnHl1StepColor = ptnColors->child(2)->data(1, Qt::BackgroundRole).value(); pl->ptnHl2StepColor = ptnColors->child(3)->data(1, Qt::BackgroundRole).value(); pl->ptnCurTextColor = ptnColors->child(4)->data(1, Qt::BackgroundRole).value(); pl->ptnCurStepColor = ptnColors->child(5)->data(1, Qt::BackgroundRole).value(); pl->ptnCurEditStepColor = ptnColors->child(6)->data(1, Qt::BackgroundRole).value(); pl->ptnCurCellColor = ptnColors->child(7)->data(1, Qt::BackgroundRole).value(); pl->ptnPlayStepColor = ptnColors->child(8)->data(1, Qt::BackgroundRole).value(); pl->ptnSelCellColor = ptnColors->child(9)->data(1, Qt::BackgroundRole).value(); pl->ptnHovCellColor = ptnColors->child(10)->data(1, Qt::BackgroundRole).value(); pl->ptnDefStepNumColor = ptnColors->child(11)->data(1, Qt::BackgroundRole).value(); pl->ptnHl1StepNumColor = ptnColors->child(12)->data(1, Qt::BackgroundRole).value(); pl->ptnHl2StepNumColor = ptnColors->child(13)->data(1, Qt::BackgroundRole).value(); pl->ptnNoteColor = ptnColors->child(14)->data(1, Qt::BackgroundRole).value(); pl->ptnInstColor = ptnColors->child(15)->data(1, Qt::BackgroundRole).value(); pl->ptnVolColor = ptnColors->child(16)->data(1, Qt::BackgroundRole).value(); pl->ptnEffColor = ptnColors->child(17)->data(1, Qt::BackgroundRole).value(); pl->ptnErrorColor = ptnColors->child(18)->data(1, Qt::BackgroundRole).value(); pl->ptnHeaderTextColor = ptnColors->child(19)->data(1, Qt::BackgroundRole).value(); pl->ptnHeaderRowColor = ptnColors->child(20)->data(1, Qt::BackgroundRole).value(); pl->ptnMaskColor = ptnColors->child(21)->data(1, Qt::BackgroundRole).value(); pl->ptnBorderColor = ptnColors->child(22)->data(1, Qt::BackgroundRole).value(); pl->ptnMuteColor = ptnColors->child(23)->data(1, Qt::BackgroundRole).value(); pl->ptnUnmuteColor = ptnColors->child(24)->data(1, Qt::BackgroundRole).value(); pl->ptnBackColor = ptnColors->child(25)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* odrColors = ui->colorsTreeWidget->topLevelItem(1); pl->odrDefTextColor = odrColors->child(0)->data(1, Qt::BackgroundRole).value(); pl->odrDefRowColor = odrColors->child(1)->data(1, Qt::BackgroundRole).value(); pl->odrCurTextColor = odrColors->child(2)->data(1, Qt::BackgroundRole).value(); pl->odrCurRowColor = odrColors->child(3)->data(1, Qt::BackgroundRole).value(); pl->odrCurEditRowColor = odrColors->child(4)->data(1, Qt::BackgroundRole).value(); pl->odrCurCellColor = odrColors->child(5)->data(1, Qt::BackgroundRole).value(); pl->odrPlayRowColor = odrColors->child(6)->data(1, Qt::BackgroundRole).value(); pl->odrSelCellColor = odrColors->child(7)->data(1, Qt::BackgroundRole).value(); pl->odrHovCellColor = odrColors->child(8)->data(1, Qt::BackgroundRole).value(); pl->odrRowNumColor = odrColors->child(9)->data(1, Qt::BackgroundRole).value(); pl->odrHeaderTextColor = odrColors->child(10)->data(1, Qt::BackgroundRole).value(); pl->odrHeaderRowColor = odrColors->child(11)->data(1, Qt::BackgroundRole).value(); pl->odrBorderColor = odrColors->child(12)->data(1, Qt::BackgroundRole).value(); pl->odrBackColor = odrColors->child(13)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* ilistColors = ui->colorsTreeWidget->topLevelItem(2); pl->ilistTextColor = ilistColors->child(0)->data(1, Qt::BackgroundRole).value(); pl->ilistBackColor = ilistColors->child(1)->data(1, Qt::BackgroundRole).value(); pl->ilistSelBackColor = ilistColors->child(2)->data(1, Qt::BackgroundRole).value(); pl->ilistHovBackColor = ilistColors->child(3)->data(1, Qt::BackgroundRole).value(); pl->ilistHovSelBackColor = ilistColors->child(4)->data(1, Qt::BackgroundRole).value(); QTreeWidgetItem* wavColors = ui->colorsTreeWidget->topLevelItem(3); pl->wavBackColor = wavColors->child(0)->data(1, Qt::BackgroundRole).value(); pl->wavDrawColor = wavColors->child(1)->data(1, Qt::BackgroundRole).value(); configLocked->setPatternEditorHeaderFont(ui->ptnHdFontComboBox->currentFont().family().toStdString()); configLocked->setPatternEditorHeaderFontSize(ui->ptnHdFontSizeComboBox->currentText().toInt()); configLocked->setPatternEditorRowsFont(ui->ptnRowFontComboBox->currentFont().family().toStdString()); configLocked->setPatternEditorRowsFontSize(ui->ptnRowFontSizeComboBox->currentText().toInt()); configLocked->setOrderListHeaderFont(ui->odrHdFontComboBox->currentFont().family().toStdString()); configLocked->setOrderListHeaderFontSize(ui->odrHdFontSizeComboBox->currentText().toInt()); configLocked->setOrderListRowsFont(ui->odrRowFontComboBox->currentFont().family().toStdString()); configLocked->setOrderListRowsFontSize(ui->odrRowFontSizeComboBox->currentText().toInt()); if (changedEmu) { QMessageBox::information(this, tr("Configuration"), tr("The change of emulator will be effective after restarting the program.")); } } /***** General *****/ void ConfigurationDialog::on_generalSettingsListWidget_itemSelectionChanged() { QString text; switch (ui->generalSettingsListWidget->currentRow()) { case 0: // Warp cursor text = tr("Warp the cursor around the edges of the pattern editor."); break; case 1: // Warp across orders text = tr("Move to previous or next order when reaching top or bottom in the pattern editor."); break; case 2: // Show row numbers in hex text = tr("Display row numbers and the playback position on the status bar in hexadecimal."); break; case 3: // Preview previous/next orders text = tr("Preview previous and next orders in the pattern editor."); break; case 4: // Backup modeles text = tr("Create a backup copy of the existing file when saving a module."); break; case 5: // Don't select on double click text = tr("Don't select the whole track when double-clicking in the pattern editor."); break; case 6: // Reverse FM volume order text = tr("Reverse the order of FM volume so that 00 is the quietest in the pattern editor."); break; case 7: // Move cursor to right text = tr("Move the cursor to right after entering effects in the pattern editor."); break; case 8: // Retrieve channel state text = tr("Reconstruct the current channel's state from previous orders upon playing."); break; case 9: // Enable translation text = tr("Translate to your language from the next launch. " "See readme to check supported languages."); break; case 10: // Show FM detune as signed text = tr("Display FM detune values as signed numbers in the FM envelope editor."); break; case 11: // Show wave visual text = tr("Enable an oscilloscope which displays a waveform of the sound output."); break; case 12: // Fill 00 to effect value text = tr("Fill 00 to effect value column upon entering effect id."); break; case 13: // Autoset instrument text = tr("Set current instrument upon entering note."); break; case 14: // Move cursor by horizontal scroll text = tr("Move the cursor position by cell with horizontal scroll bar in the order list and the pattern editor."); break; default: text = ""; break; } ui->descPlainTextEdit->setPlainText(tr("Description: %1").arg(text)); } /***** Mixer *****/ void ConfigurationDialog::on_mixerResetPushButton_clicked() { ui->fmMixerSlider->setValue(0); ui->ssgMixerSlider->setValue(0); } /***** MIDI *****/ void ConfigurationDialog::on_midiInputChoiceButton_clicked() { QToolButton *button = ui->midiInputChoiceButton; QMenu menu; QAction *action; MidiInterface &intf = MidiInterface::instance(); std::vector ports = intf.getRealInputPorts(); bool vport = intf.supportsVirtualPort(); if (vport) { ui->midiInputNameLine->setPlaceholderText(tr("Virtual port")); action = menu.addAction(tr("Virtual port")); action->setData(QString()); } for (unsigned i = 0, n = ports.size(); i < n; ++i) { if (i == 0 && vport) menu.addSeparator(); QString portName = QString::fromStdString(ports[i]); action = menu.addAction(portName); action->setData(portName); } QAction *choice = menu.exec(button->mapToGlobal(button->rect().bottomLeft())); if (choice) { QString portName = choice->data().toString(); ui->midiInputNameLine->setText(portName); } } /*****Formats *****/ void ConfigurationDialog::on_addEnvelopeSetPushButton_clicked() { auto name = tr("Set %1").arg(fmEnvelopeTexts_.size() + 1); fmEnvelopeTexts_.push_back({ name.toUtf8().toStdString(), std::vector() }); updateEnvelopeSetUi(); for (int i = ui->envelopeTypeListWidget->count() - 1; i >= 0; --i) { if (ui->envelopeTypeListWidget->item(i)->text() == name) { ui->envelopeTypeListWidget->setCurrentRow(i); break; } } } void ConfigurationDialog::on_removeEnvelopeSetpushButton_clicked() { fmEnvelopeTexts_.erase(fmEnvelopeTexts_.begin() + ui->envelopeTypeListWidget->currentRow()); updateEnvelopeSetUi(); } void ConfigurationDialog::on_editEnvelopeSetPushButton_clicked() { size_t row = static_cast(ui->envelopeTypeListWidget->currentRow()); FMEnvelopeSetEditDialog diag(fmEnvelopeTexts_.at(row).texts); diag.setWindowTitle(diag.windowTitle() + ": " + ui->envelopeSetNameLineEdit->text()); if (diag.exec() == QDialog::Accepted) { fmEnvelopeTexts_.at(row).texts = diag.getSet(); } } void ConfigurationDialog::on_envelopeSetNameLineEdit_textChanged(const QString &arg1) { fmEnvelopeTexts_.at(static_cast(ui->envelopeTypeListWidget->currentRow())).name = arg1.toStdString(); ui->envelopeTypeListWidget->currentItem()->setText(arg1); } void ConfigurationDialog::on_envelopeTypeListWidget_currentRowChanged(int currentRow) { if (currentRow == -1) { ui->editEnvelopeSetPushButton->setEnabled(false); ui->removeEnvelopeSetpushButton->setEnabled(false); ui->envelopeSetNameLineEdit->setEnabled(false); } else { ui->editEnvelopeSetPushButton->setEnabled(true); ui->removeEnvelopeSetpushButton->setEnabled(true); ui->envelopeSetNameLineEdit->setEnabled(true); ui->envelopeSetNameLineEdit->setText(ui->envelopeTypeListWidget->item(currentRow)->text()); } } void ConfigurationDialog::updateEnvelopeSetUi() { std::sort(fmEnvelopeTexts_.begin(), fmEnvelopeTexts_.end(), [](const FMEnvelopeText& a, const FMEnvelopeText& b) -> bool { return (a.name < b.name); }); ui->envelopeTypeListWidget->clear(); for (auto& texts : fmEnvelopeTexts_) ui->envelopeTypeListWidget->addItem( QString::fromUtf8(texts.name.c_str(), static_cast(texts.name.length()))); } /***** Keys *****/ void ConfigurationDialog::on_keyboardTypeComboBox_currentIndexChanged(int index) { Q_UNUSED(index) bool enableCustomLayoutInterface = ui->keyboardTypeComboBox->currentIndex() == 0; ui->lowHighKeysTabWidget->setEnabled(enableCustomLayoutInterface); ui->customLayoutResetButton->setEnabled(enableCustomLayoutInterface); } void ConfigurationDialog::on_customLayoutResetButton_clicked() { std::unordered_map QWERTYLayoutMapping = config_.lock()->mappingLayouts.at (Configuration::KeyboardLayout::QWERTY); std::unordered_map::const_iterator QWERTYLayoutMappingIterator = QWERTYLayoutMapping.begin(); while (QWERTYLayoutMappingIterator != QWERTYLayoutMapping.end()) { customLayoutKeysMap.at(QWERTYLayoutMappingIterator->second)->setKeySequence(QKeySequence(QString::fromStdString(QWERTYLayoutMappingIterator->first))); QWERTYLayoutMappingIterator++; } } /***** Appearance *****/ void ConfigurationDialog::on_colorEditPushButton_clicked() { QTreeWidgetItem* item = ui->colorsTreeWidget->currentItem(); if (item == nullptr || item->parent() == nullptr) return; QColorDialog diag(item->data(1, Qt::BackgroundRole).value()); diag.setOption(QColorDialog::ShowAlphaChannel); if (diag.exec() == QDialog::Accepted) item->setData(1, Qt::BackgroundRole, diag.currentColor()); } void ConfigurationDialog::on_colorLoadPushButton_clicked() { QString file = QFileDialog::getOpenFileName(this, tr("Open color scheme"), QApplication::applicationDirPath() + "/skins", tr("ini file (*.ini)")); if (file.isNull()) return; if (ColorPaletteHandler::loadPalette(file, palette_)) { updateColorTree(); } else { QMessageBox::critical(this, tr("Error"), tr("An unknown error occurred while loading the color scheme.")); } } void ConfigurationDialog::on_colorSavePushButton_clicked() { QString file = QFileDialog::getSaveFileName(this, tr("Save color scheme"), QApplication::applicationDirPath() + "/skins", tr("ini file (*.ini)")); if (file.isNull()) return; if (!file.endsWith(".ini")) file += ".ini"; // For linux if (!ColorPaletteHandler::savePalette(file, palette_)) QMessageBox::critical(this, tr("Error"), tr("Failed to save the color scheme.")); } void ConfigurationDialog::updateColorTree() { auto* pl = palette_.lock().get(); QTreeWidgetItem* ptnColors = ui->colorsTreeWidget->topLevelItem(0); ptnColors->child(0)->setData(1, Qt::BackgroundRole, pl->ptnDefTextColor); ptnColors->child(1)->setData(1, Qt::BackgroundRole, pl->ptnDefStepColor); ptnColors->child(2)->setData(1, Qt::BackgroundRole, pl->ptnHl1StepColor); ptnColors->child(3)->setData(1, Qt::BackgroundRole, pl->ptnHl2StepColor); ptnColors->child(4)->setData(1, Qt::BackgroundRole, pl->ptnCurTextColor); ptnColors->child(5)->setData(1, Qt::BackgroundRole, pl->ptnCurStepColor); ptnColors->child(6)->setData(1, Qt::BackgroundRole, pl->ptnCurEditStepColor); ptnColors->child(7)->setData(1, Qt::BackgroundRole, pl->ptnCurCellColor); ptnColors->child(8)->setData(1, Qt::BackgroundRole, pl->ptnPlayStepColor); ptnColors->child(9)->setData(1, Qt::BackgroundRole, pl->ptnSelCellColor); ptnColors->child(10)->setData(1, Qt::BackgroundRole, pl->ptnHovCellColor); ptnColors->child(11)->setData(1, Qt::BackgroundRole, pl->ptnDefStepNumColor); ptnColors->child(12)->setData(1, Qt::BackgroundRole, pl->ptnHl1StepNumColor); ptnColors->child(13)->setData(1, Qt::BackgroundRole, pl->ptnHl2StepNumColor); ptnColors->child(14)->setData(1, Qt::BackgroundRole, pl->ptnNoteColor); ptnColors->child(15)->setData(1, Qt::BackgroundRole, pl->ptnInstColor); ptnColors->child(16)->setData(1, Qt::BackgroundRole, pl->ptnVolColor); ptnColors->child(17)->setData(1, Qt::BackgroundRole, pl->ptnEffColor); ptnColors->child(18)->setData(1, Qt::BackgroundRole, pl->ptnErrorColor); ptnColors->child(19)->setData(1, Qt::BackgroundRole, pl->ptnHeaderTextColor); ptnColors->child(20)->setData(1, Qt::BackgroundRole, pl->ptnHeaderRowColor); ptnColors->child(21)->setData(1, Qt::BackgroundRole, pl->ptnMaskColor); ptnColors->child(22)->setData(1, Qt::BackgroundRole, pl->ptnBorderColor); ptnColors->child(23)->setData(1, Qt::BackgroundRole, pl->ptnMuteColor); ptnColors->child(24)->setData(1, Qt::BackgroundRole, pl->ptnUnmuteColor); ptnColors->child(25)->setData(1, Qt::BackgroundRole, pl->ptnBackColor); QTreeWidgetItem* odrColors = ui->colorsTreeWidget->topLevelItem(1); odrColors->child(0)->setData(1, Qt::BackgroundRole, pl->odrDefTextColor); odrColors->child(1)->setData(1, Qt::BackgroundRole, pl->odrDefRowColor); odrColors->child(2)->setData(1, Qt::BackgroundRole, pl->odrCurTextColor); odrColors->child(3)->setData(1, Qt::BackgroundRole, pl->odrCurRowColor); odrColors->child(4)->setData(1, Qt::BackgroundRole, pl->odrCurEditRowColor); odrColors->child(5)->setData(1, Qt::BackgroundRole, pl->odrCurCellColor); odrColors->child(6)->setData(1, Qt::BackgroundRole, pl->odrPlayRowColor); odrColors->child(7)->setData(1, Qt::BackgroundRole, pl->odrSelCellColor); odrColors->child(8)->setData(1, Qt::BackgroundRole, pl->odrHovCellColor); odrColors->child(9)->setData(1, Qt::BackgroundRole, pl->odrRowNumColor); odrColors->child(10)->setData(1, Qt::BackgroundRole, pl->odrHeaderTextColor); odrColors->child(11)->setData(1, Qt::BackgroundRole, pl->odrHeaderRowColor); odrColors->child(12)->setData(1, Qt::BackgroundRole, pl->odrBorderColor); odrColors->child(13)->setData(1, Qt::BackgroundRole, pl->odrBackColor); QTreeWidgetItem* ilistColors = ui->colorsTreeWidget->topLevelItem(2); ilistColors->child(0)->setData(1, Qt::BackgroundRole, pl->ilistTextColor); ilistColors->child(1)->setData(1, Qt::BackgroundRole, pl->ilistBackColor); ilistColors->child(2)->setData(1, Qt::BackgroundRole, pl->ilistSelBackColor); ilistColors->child(3)->setData(1, Qt::BackgroundRole, pl->ilistHovBackColor); ilistColors->child(4)->setData(1, Qt::BackgroundRole, pl->ilistHovSelBackColor); QTreeWidgetItem* wavColors = ui->colorsTreeWidget->topLevelItem(3); wavColors->child(0)->setData(1, Qt::BackgroundRole, pl->wavBackColor); wavColors->child(1)->setData(1, Qt::BackgroundRole, pl->wavDrawColor); } BambooTracker-0.3.5/BambooTracker/gui/configuration_dialog.hpp000066400000000000000000000041431362177441300244420ustar00rootroot00000000000000#ifndef CONFIGURATION_DIALOG_HPP #define CONFIGURATION_DIALOG_HPP #include #include #include #include #include #include #include "configuration.hpp" #include "color_palette.hpp" #include "enum_hash.hpp" #include "misc.hpp" namespace Ui { class ConfigurationDialog; } class ConfigurationDialog : public QDialog { Q_OBJECT public: ConfigurationDialog(std::weak_ptr config, std::weak_ptr palette, std::string curApi, std::vector apis, QWidget *parent = nullptr); ~ConfigurationDialog() override; signals: void applyPressed(); private slots: void on_ConfigurationDialog_accepted(); private: Ui::ConfigurationDialog *ui; std::weak_ptr config_; std::weak_ptr palette_; inline Qt::CheckState toCheckState(bool enabled) { return enabled ? Qt::Checked : Qt::Unchecked; } inline bool fromCheckState(Qt::CheckState state) { return (state == Qt::Checked) ? true : false; } std::unordered_map customLayoutKeysMap; /***** General *****/ private slots: void on_generalSettingsListWidget_itemSelectionChanged(); /***** Mixer *****/ private slots: void on_mixerResetPushButton_clicked(); /***** MIDI *****/ private slots: void on_midiInputChoiceButton_clicked(); /***** Formats *****/ private: std::vector fmEnvelopeTexts_; private slots: void on_addEnvelopeSetPushButton_clicked(); void on_removeEnvelopeSetpushButton_clicked(); void on_editEnvelopeSetPushButton_clicked(); void on_envelopeSetNameLineEdit_textChanged(const QString &arg1); void on_envelopeTypeListWidget_currentRowChanged(int currentRow); void updateEnvelopeSetUi(); /***** Keys *****/ private slots: void on_keyboardTypeComboBox_currentIndexChanged(int index); void on_customLayoutResetButton_clicked(); /***** Appearance *****/ private slots: void on_colorEditPushButton_clicked(); void on_colorLoadPushButton_clicked(); void on_colorSavePushButton_clicked(); private: void updateColorTree(); }; #endif // CONFIGURATION_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/configuration_dialog.ui000066400000000000000000002656551362177441300243110ustar00rootroot00000000000000 ConfigurationDialog 0 0 523 432 Configuration Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 General Qt::Vertical 20 40 General settings Warp cursor Checked Warp across orders Checked Show row numbers in hex Checked Preview previous/next orders Checked Backup modules Checked Don't select on double click Unchecked Reverse FM volume order Checked Move cursor to right Unchecked Retrieve channel state Unchecked Enable translation Checked Show FM detune as signed Unchecked Show wave visual Checked Fill 00 to effect value Checked Autoset instrument Checked Move cursor by horizontal scroll Checked Qt::NoFocus true Description: Edit settings Page jump length 1 128 4 Sound Sample rate Qt::Vertical 20 40 Buffer length 1 500 Qt::Horizontal 1ms Qt::AlignCenter Device Real chip interface Emulation core API MIDI Device MIDI input true ... Qt::Vertical 20 210 Mixer Part 0 0 QFrame::StyledPanel QFrame::Raised 0 0 QFrame::StyledPanel QFrame::Raised The level setting for each part is valid when the mixer in the module properties is not checked. true Reset QFrame::StyledPanel QFrame::Raised Appearance Font and size Order list rows Pattern editor rows Pattern editor header Order list header 0 0 10 11 12 14 16 18 20 22 0 0 10 11 12 14 16 18 20 22 0 0 10 11 12 14 16 18 20 22 0 0 10 11 12 14 16 18 20 22 Colors Qt::Vertical 20 40 Edit QAbstractItemView::NoEditTriggers QAbstractItemView::SelectItems Item Color Pattern editor Default step text Default step background Highlighted step 1 background Highlighted step 2 background Current step text Current step background Current editing step background Current cell background Current playing step background Selection background Hovered cell background Default step number Highlighted step 1 number Highlighted step 2 number Note text Instrument text Volume text Effect text Error text Header text Header background Mask Border Mute Unmute Background Order list Default row text Default row background Current row text Current row background Current editing row background Current cell background Current playing row background Selection background Hovered cell background Row number Header text Header background Border Background Instrument list Default text Background Selected background Hovered background Selected hovered background Oscilloscope Background Foreground Save Load Formats FM envelope text Add false Edit false Remove Qt::Vertical 20 40 false Qt::Vertical 20 40 Keys 0 0 16777215 216 Note entry layout Custom 0 Custom QWERTY QWERTZ AZERTY 0 0 90 16777215 Reset 0 0 16777215 120 QLabel { border: 1px solid rgb(0, 0, 0); } QTabWidget::North QTabWidget::Rounded 0 false false Low 0 2 0 0 16 0 16777215 50 0 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame QFrame::Plain C Qt::AlignBottom|Qt::AlignHCenter false 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); QFrame::NoFrame C# false Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true false background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame D Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); D# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); E Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); F Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); F# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 false true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); G Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); G# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); A Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); A# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); B Qt::AlignBottom|Qt::AlignHCenter Qt::Horizontal QSizePolicy::Fixed 5 20 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); C Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); C# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); D Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 16 0 High 0 2 16 0 16 0 16 0 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); C# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); F Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); D Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); G# Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 16 0 16 0 75 true false background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame D Qt::AlignBottom|Qt::AlignHCenter 16 0 75 false true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); G Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); D# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); A Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); C Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); E Qt::AlignBottom|Qt::AlignHCenter 16 0 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); QFrame::NoFrame C# false Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); A# Qt::AlignBottom|Qt::AlignHCenter 16 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); B Qt::AlignBottom|Qt::AlignHCenter 0 0 16 0 16777215 50 0 0 75 true background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); QFrame::NoFrame QFrame::Plain C Qt::AlignBottom|Qt::AlignHCenter false 16 0 75 true background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); F# Qt::AlignBottom|Qt::AlignHCenter Qt::Horizontal QSizePolicy::Fixed 5 20 0 0 Special keys Key off Octave up Octave down Echo buffer LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
tabWidget generalSettingsListWidget pageJumpLengthSpinBox emulatorComboBox soundAPIComboBox soundDeviceComboBox realChipComboBox sampleRateComboBox bufferLengthHorizontalSlider midiInputNameLine midiInputChoiceButton mixerResetPushButton colorsTreeWidget colorLoadPushButton colorSavePushButton colorEditPushButton ptnHdFontComboBox ptnHdFontSizeComboBox ptnRowFontComboBox ptnRowFontSizeComboBox odrHdFontComboBox odrHdFontSizeComboBox odrRowFontComboBox odrRowFontSizeComboBox envelopeTypeListWidget addEnvelopeSetPushButton removeEnvelopeSetpushButton envelopeSetNameLineEdit editEnvelopeSetPushButton keyboardTypeComboBox customLayoutResetButton lowHighKeysTabWidget lowCEdit lowCSEdit lowDEdit lowDSEdit lowEEdit lowFEdit lowFSEdit lowGEdit lowGSEdit lowAEdit lowASEdit lowBEdit lowHighCEdit lowHighCSEdit lowHighDEdit highCEdit highCSEdit highDEdit highDSEdit highEEdit highFEdit highFSEdit highGEdit highGSEdit highAEdit highASEdit highBEdit highHighCEdit highHighCSEdit highHighDEdit keyOffKeySequenceEdit octaveUpKeySequenceEdit octaveDownKeySequenceEdit echoBufferKeySequenceEdit buttonBox accepted() ConfigurationDialog accept() 266 395 157 274 buttonBox rejected() ConfigurationDialog reject() 334 395 286 274 envelopeTypeListWidget itemDoubleClicked(QListWidgetItem*) editEnvelopeSetPushButton click() 185 189 412 262
BambooTracker-0.3.5/BambooTracker/gui/configuration_handler.cpp000066400000000000000000000476501362177441300246250ustar00rootroot00000000000000#include "configuration_handler.hpp" #include #include #include #include "jam_manager.hpp" #include"enum_hash.hpp" // config path (*nix): ~/.config//.ini const QString ConfigurationHandler::organization = "BambooTracker"; const QString ConfigurationHandler::application = "BambooTracker"; ConfigurationHandler::ConfigurationHandler() {} bool ConfigurationHandler::saveConfiguration(std::weak_ptr config) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ConfigurationHandler::organization, ConfigurationHandler::application); std::shared_ptr configLocked = config.lock(); // Internal // settings.beginGroup("Internal"); settings.setValue("mainWindowWidth", configLocked->getMainWindowWidth()); settings.setValue("mainWindowHeight", configLocked->getMainWindowHeight()); settings.setValue("mainWindowMaximized", configLocked->getMainWindowMaximized()); settings.setValue("mainWindowX", configLocked->getMainWindowX()); settings.setValue("mainWindowY", configLocked->getMainWindowY()); settings.setValue("mainWindowVerticalSplit", configLocked->getMainWindowVerticalSplit()); settings.setValue("instrumentFMWindowWidth", configLocked->getInstrumentFMWindowWidth()); settings.setValue("instrumentFMWindowHeight", configLocked->getInstrumentFMWindowHeight()); settings.setValue("instrumentSSGWindowWidth", configLocked->getInstrumentSSGWindowWidth()); settings.setValue("instrumentSSGWindowHeight", configLocked->getInstrumentSSGWindowHeight()); settings.setValue("followMode", configLocked->getFollowMode()); settings.setValue("workingDirectory", QString::fromStdString(configLocked->getWorkingDirectory())); settings.setValue("instrumentOpenFormat", configLocked->getInstrumentOpenFormat()); settings.setValue("bankOpenFormat", configLocked->getBankOpenFormat()); settings.endGroup(); // General // // General settings settings.beginGroup("General"); settings.setValue("warpCursor", configLocked->getWarpCursor()); settings.setValue("warpAcrossOrders", configLocked->getWarpAcrossOrders()); settings.setValue("showRowNumberInHex", configLocked->getShowRowNumberInHex()); settings.setValue("showPreviousNextOrders", configLocked->getShowPreviousNextOrders()); settings.setValue("backupModule", configLocked->getBackupModules()); settings.setValue("dontSelectOnDoubleClick", configLocked->getDontSelectOnDoubleClick()); settings.setValue("reverseFMVolumeOrder", configLocked->getReverseFMVolumeOrder()); settings.setValue("moveCursorToRight", configLocked->getMoveCursorToRight()); settings.setValue("retrieveChannelState", configLocked->getRetrieveChannelState()); settings.setValue("enableTranslation", configLocked->getEnableTranslation()); settings.setValue("showFMDetuneAsSigned", configLocked->getShowFMDetuneAsSigned()); settings.setValue("showWaveVisual", configLocked->getShowWaveVisual()); settings.setValue("fill00ToEffectValue", configLocked->getFill00ToEffectValue()); settings.setValue("autosetInstrument", configLocked->getAutosetInstrument()); settings.setValue("moveCursorByHScroll", configLocked->getMoveCursorByHorizontalScroll()); settings.endGroup(); // Edit settings settings.beginGroup("Editing"); settings.setValue("pageJumpLength", static_cast(configLocked->getPageJumpLength())); settings.setValue("editableStep", static_cast(configLocked->getEditableStep())); settings.setValue("keyRepetition", configLocked->getKeyRepetition()); settings.endGroup(); // Keys settings.beginGroup("Keys"); settings.setValue("keyOffKey", QString::fromUtf8(configLocked->getKeyOffKey().c_str(), static_cast(configLocked->getKeyOffKey().length()))); settings.setValue("octaveUpKey", QString::fromUtf8(configLocked->getOctaveUpKey().c_str(), static_cast(configLocked->getOctaveUpKey().length()))); settings.setValue("octaveDownKey", QString::fromUtf8(configLocked->getOctaveDownKey().c_str(), static_cast(configLocked->getOctaveDownKey().length()))); settings.setValue("echoBufferKey", QString::fromUtf8(configLocked->getEchoBufferKey().c_str(), static_cast(configLocked->getEchoBufferKey().length()))); settings.setValue("noteEntryLayout", static_cast(configLocked->getNoteEntryLayout())); std::unordered_map customLayoutMapping = configLocked->getCustomLayoutKeys(); const std::unordered_map keyToNameMapping = { {JamKey::LowC, "lowC"}, {JamKey::LowCS, "lowCS"}, {JamKey::LowD, "lowD"}, {JamKey::LowDS, "lowDS"}, {JamKey::LowE, "lowE"}, {JamKey::LowF, "lowF"}, {JamKey::LowFS, "lowFS"}, {JamKey::LowG, "lowG"}, {JamKey::LowGS, "lowGS"}, {JamKey::LowA, "lowA"}, {JamKey::LowAS, "lowAS"}, {JamKey::LowB, "lowB"}, {JamKey::LowC2, "lowHighC"}, {JamKey::LowCS2, "lowHighCS"}, {JamKey::LowD2, "lowHighD"}, {JamKey::HighC, "highC"}, {JamKey::HighCS, "highCS"}, {JamKey::HighD, "highD"}, {JamKey::HighDS, "highDS"}, {JamKey::HighE, "highE"}, {JamKey::HighF, "highF"}, {JamKey::HighFS, "highFS"}, {JamKey::HighG, "highG"}, {JamKey::HighGS, "highGS"}, {JamKey::HighA, "highA"}, {JamKey::HighAS, "highAS"}, {JamKey::HighB, "highB"}, {JamKey::HighC2, "highHighC"}, {JamKey::HighCS2, "highHighCS"}, {JamKey::HighD2, "highHighD"} }; std::unordered_map::const_iterator customLayoutMappingIterator = customLayoutMapping.begin(); while (customLayoutMappingIterator != customLayoutMapping.end()) { settings.setValue(QString::fromStdString("customLayout_" + keyToNameMapping.at(customLayoutMappingIterator->second)), QString::fromUtf8(customLayoutMappingIterator->first.c_str(), static_cast (customLayoutMappingIterator->first.length()))); customLayoutMappingIterator++; } settings.endGroup(); // Sound // settings.beginGroup("Sound"); settings.setValue("soundAPI", QString::fromUtf8(configLocked->getSoundAPI().c_str(), static_cast(configLocked->getSoundAPI().length()))); settings.setValue("soundDevice", QString::fromUtf8(configLocked->getSoundDevice().c_str(), static_cast(configLocked->getSoundDevice().length()))); settings.setValue("realChipInterface", static_cast(configLocked->getRealChipInterface())); settings.setValue("emulator", configLocked->getEmulator()); settings.setValue("sampleRate", static_cast(configLocked->getSampleRate())); settings.setValue("bufferLength", static_cast(configLocked->getBufferLength())); settings.endGroup(); // Midi // settings.beginGroup("Midi"); settings.setValue("inputPort", QString::fromStdString(configLocked->getMidiInputPort())); settings.endGroup(); // Mixer // settings.beginGroup("Mixer"); settings.setValue("mixerVolumeMaster", configLocked->getMixerVolumeMaster()); settings.setValue("mixerVolumeFM", configLocked->getMixerVolumeFM()); settings.setValue("mixerVolumeSSG", configLocked->getMixerVolumeSSG()); settings.endGroup(); // Input // settings.beginGroup("Input"); settings.beginWriteArray("fmEnvelopeTextMap"); int n = 0; for (auto texts : config.lock()->getFMEnvelopeTexts()) { settings.setArrayIndex(n++); settings.setValue("type", QString::fromUtf8(texts.name.c_str(), static_cast(texts.name.length()))); QStringList typeList; std::transform(texts.texts.begin(), texts.texts.end(), std::back_inserter(typeList), [](FMEnvelopeTextType type) { return QString::number(static_cast(type)); }); settings.setValue("order", typeList.join(",")); } settings.endArray(); settings.endGroup(); settings.beginGroup("Appearance"); settings.setValue("patternEditorHeaderFont", QString::fromStdString(configLocked->getPatternEditorHeaderFont())); settings.setValue("patternEditorHeaderFontSize", configLocked->getPatternEditorHeaderFontSize()); settings.setValue("patternEditorRowsFont", QString::fromStdString(configLocked->getPatternEditorRowsFont())); settings.setValue("patternEditorRowsFontSize", configLocked->getPatternEditorRowsFontSize()); settings.setValue("orderListHeaderFont", QString::fromStdString(configLocked->getOrderListHeaderFont())); settings.setValue("orderListHeaderFontSize", configLocked->getOrderListHeaderFontSize()); settings.setValue("orderListRowsFont", QString::fromStdString(configLocked->getOrderListRowsFont())); settings.setValue("orderListRowsFontSize", configLocked->getOrderListRowsFontSize()); settings.endGroup(); return true; } catch (...) { return false; } } bool ConfigurationHandler::loadConfiguration(std::weak_ptr config) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ConfigurationHandler::organization, ConfigurationHandler::application); std::shared_ptr configLocked = config.lock(); // Internal // settings.beginGroup("Internal"); configLocked->setMainWindowWidth(settings.value("mainWindowWidth", configLocked->getMainWindowWidth()).toInt()); configLocked->setMainWindowHeight(settings.value("mainWindowHeight", configLocked->getMainWindowHeight()).toInt()); configLocked->setMainWindowMaximized(settings.value("mainWindowMaximized", configLocked->getMainWindowMaximized()).toBool()); configLocked->setMainWindowX(settings.value("mainWindowX", configLocked->getMainWindowX()).toInt()); configLocked->setMainWindowY(settings.value("mainWindowY", configLocked->getMainWindowY()).toInt()); configLocked->setMainWindowVerticalSplit(settings.value("mainWindowVerticalSplit", configLocked->getMainWindowVerticalSplit()).toInt()); configLocked->setInstrumentFMWindowWidth(settings.value("instrumentFMWindowWidth", configLocked->getInstrumentFMWindowWidth()).toInt()); configLocked->setInstrumentFMWindowHeight(settings.value("instrumentFMWindowHeight", configLocked->getInstrumentFMWindowHeight()).toInt()); configLocked->setInstrumentSSGWindowWidth(settings.value("instrumentSSGWindowWidth", configLocked->getInstrumentSSGWindowWidth()).toInt()); configLocked->setInstrumentSSGWindowHeight(settings.value("instrumentSSGWindowHeight", configLocked->getInstrumentSSGWindowHeight()).toInt()); configLocked->setFollowMode(settings.value("followMode", configLocked->getFollowMode()).toBool()); configLocked->setWorkingDirectory(settings.value("workingDirectory", QString::fromStdString(configLocked->getWorkingDirectory())).toString().toStdString()); configLocked->setInstrumentOpenFormat(settings.value("instrumentOpenFormat", configLocked->getInstrumentOpenFormat()).toInt()); configLocked->setBankOpenFormat(settings.value("bankOpenFormat", configLocked->getBankOpenFormat()).toInt()); settings.endGroup(); // General // // General settings settings.beginGroup("General"); configLocked->setWarpCursor(settings.value("warpCursor", configLocked->getWarpCursor()).toBool()); configLocked->setWarpAcrossOrders(settings.value("warpAcrossOrders", configLocked->getWarpAcrossOrders()).toBool()); configLocked->setShowRowNumberInHex(settings.value("showRowNumberInHex", configLocked->getShowRowNumberInHex()).toBool()); configLocked->setShowPreviousNextOrders(settings.value("showPreviousNextOrders", configLocked->getShowPreviousNextOrders()).toBool()); configLocked->setBackupModules(settings.value("backupModule", configLocked->getBackupModules()).toBool()); configLocked->setDontSelectOnDoubleClick(settings.value("dontSelectOnDoubleClick", configLocked->getDontSelectOnDoubleClick()).toBool()); configLocked->setReverseFMVolumeOrder(settings.value("reverseFMVolumeOrder", configLocked->getReverseFMVolumeOrder()).toBool()); configLocked->setMoveCursorToRight(settings.value("moveCursorToRight", configLocked->getMoveCursorToRight()).toBool()); configLocked->setRetrieveChannelState(settings.value("retrieveChannelState", configLocked->getRetrieveChannelState()).toBool()); configLocked->setEnableTranslation(settings.value("enableTranslation", configLocked->getEnableTranslation()).toBool()); configLocked->setShowFMDetuneAsSigned(settings.value("showFMDetuneAsSigned", configLocked->getShowFMDetuneAsSigned()).toBool()); configLocked->setShowWaveVisual(settings.value("showWaveVisual", configLocked->getShowWaveVisual()).toBool()); configLocked->setFill00ToEffectValue(settings.value("fill00ToEffectValue", configLocked->getFill00ToEffectValue()).toBool()); configLocked->setAutosetInstrument(settings.value("autosetInstrument", configLocked->getAutosetInstrument()).toBool()); configLocked->setMoveCursorByHorizontalScroll(settings.value("moveCursorByHScroll", configLocked->getMoveCursorByHorizontalScroll()).toBool()); settings.endGroup(); // Edit settings settings.beginGroup("Editing"); QVariant pageJumpLengthWorkaround; pageJumpLengthWorkaround.setValue(configLocked->getPageJumpLength()); configLocked->setPageJumpLength(static_cast(settings.value("pageJumpLength", pageJumpLengthWorkaround).toInt())); QVariant editableStepWorkaround; editableStepWorkaround.setValue(configLocked->getEditableStep()); configLocked->setEditableStep(static_cast(settings.value("editableStep", editableStepWorkaround).toInt())); configLocked->setKeyRepetition(settings.value("keyRepetition", configLocked->getKeyRepetition()).toBool()); settings.endGroup(); // Keys settings.beginGroup("Keys"); configLocked->setKeyOffKey(settings.value("keyOffKey", QString::fromStdString(configLocked->getKeyOffKey())).toString().toUtf8().toStdString()); configLocked->setOctaveUpKey(settings.value("octaveUpKey", QString::fromStdString(configLocked->getOctaveUpKey())).toString().toUtf8().toStdString()); configLocked->setOctaveDownKey(settings.value("octaveDownKey", QString::fromStdString(configLocked->getOctaveDownKey())).toString().toUtf8().toStdString()); configLocked->setEchoBufferKey(settings.value("echoBufferKey", QString::fromStdString(configLocked->getEchoBufferKey())).toString().toUtf8().toStdString()); configLocked->setNoteEntryLayout(static_cast( settings.value("noteEntryLayout", static_cast(configLocked->getNoteEntryLayout())).toInt())); std::unordered_map customLayoutNewKeys; const std::unordered_map nameToKeyMapping = { {"lowC", JamKey::LowC}, {"lowCS", JamKey::LowCS}, {"lowD", JamKey::LowD}, {"lowDS", JamKey::LowDS}, {"lowE", JamKey::LowE}, {"lowF", JamKey::LowF}, {"lowFS", JamKey::LowFS}, {"lowG", JamKey::LowG}, {"lowGS", JamKey::LowGS}, {"lowA", JamKey::LowA}, {"lowAS", JamKey::LowAS}, {"lowB", JamKey::LowB}, {"lowHighC", JamKey::LowC2}, {"lowHighCS", JamKey::LowCS2}, {"lowHighD", JamKey::LowD2}, {"highC", JamKey::HighC}, {"highCS", JamKey::HighCS}, {"highD", JamKey::HighD}, {"highDS", JamKey::HighDS}, {"highE", JamKey::HighE}, {"highF", JamKey::HighF}, {"highFS", JamKey::HighFS}, {"highG", JamKey::HighG}, {"highGS", JamKey::HighGS}, {"highA", JamKey::HighA}, {"highAS", JamKey::HighAS}, {"highB", JamKey::HighB}, {"highHighC", JamKey::HighC2}, {"highHighCS", JamKey::HighCS2}, {"highHighD", JamKey::HighD2} }; std::unordered_map::const_iterator nameToKeyMappingIterator = nameToKeyMapping.begin(); while (nameToKeyMappingIterator != nameToKeyMapping.end()) { JamKey currentlyWantedJamKey = nameToKeyMappingIterator->second; customLayoutNewKeys[ settings.value(QString::fromStdString("customLayout_" + nameToKeyMappingIterator->first), QString::fromStdString((*std::find_if (configLocked->mappingLayouts.at(Configuration::QWERTY).begin(), configLocked->mappingLayouts.at(Configuration::QWERTY).end(), [currentlyWantedJamKey](const std::pair& t) -> bool { return (t.second) == currentlyWantedJamKey;}) ).first)).toString().toUtf8().toStdString()] = currentlyWantedJamKey; nameToKeyMappingIterator++; } configLocked->setCustomLayoutKeys(customLayoutNewKeys); settings.endGroup(); // Sound // settings.beginGroup("Sound"); configLocked->setSoundAPI(settings.value("soundAPI", QString::fromStdString(configLocked->getSoundAPI())).toString().toUtf8().toStdString()); configLocked->setSoundDevice(settings.value("soundDevice", QString::fromStdString(configLocked->getSoundDevice())).toString().toUtf8().toStdString()); configLocked->setRealChipInterface(static_cast( settings.value("realChipInterface", static_cast(configLocked->getRealChipInterface())).toInt())); configLocked->setEmulator(settings.value("emulator", configLocked->getEmulator()).toInt()); QVariant sampleRateWorkaround; sampleRateWorkaround.setValue(configLocked->getSampleRate()); configLocked->setSampleRate(static_cast(settings.value("sampleRate", sampleRateWorkaround).toInt())); QVariant bufferLengthWorkaround; bufferLengthWorkaround.setValue(configLocked->getBufferLength()); configLocked->setBufferLength(static_cast(settings.value("bufferLength", bufferLengthWorkaround).toInt())); settings.endGroup(); // Midi // settings.beginGroup("Midi"); configLocked->setMidiInputPort(settings.value("inputPort").toString().toStdString()); settings.endGroup(); // Mixer // settings.beginGroup("Mixer"); configLocked->setMixerVolumeMaster(settings.value("mixerVolumeMaster", configLocked->getMixerVolumeMaster()).toInt()); configLocked->setMixerVolumeFM(settings.value("mixerVolumeFM", configLocked->getMixerVolumeFM()).toDouble()); configLocked->setMixerVolumeSSG(settings.value("mixerVolumeSSG", configLocked->getMixerVolumeSSG()).toDouble()); settings.endGroup(); // Input // settings.beginGroup("Input"); int size = settings.beginReadArray("fmEnvelopeTextMap"); std::vector fmEnvelopeTexts; for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); std::string type = settings.value("type").toString().toUtf8().toStdString(); std::vector data; for (auto d : settings.value("order").toString().split(",")) { data.push_back(static_cast(d.toInt())); } fmEnvelopeTexts.push_back({ type, data }); } if (!fmEnvelopeTexts.empty()) config.lock()->setFMEnvelopeTexts(fmEnvelopeTexts); settings.endArray(); settings.endGroup(); // Appearance settings.beginGroup("Appearance"); configLocked->setPatternEditorHeaderFont(settings.value("patternEditorHeaderFont", QString::fromStdString(configLocked->getPatternEditorHeaderFont())).toString().toStdString()); configLocked->setPatternEditorHeaderFontSize(settings.value("patternEditorHeaderFontSize", configLocked->getPatternEditorHeaderFontSize()).toInt()); configLocked->setPatternEditorRowsFont(settings.value("patternEditorRowsFont", QString::fromStdString(configLocked->getPatternEditorRowsFont())).toString().toStdString()); configLocked->setPatternEditorRowsFontSize(settings.value("patternEditorRowsFontSize", configLocked->getPatternEditorRowsFontSize()).toInt()); configLocked->setOrderListHeaderFont(settings.value("orderListHeaderFont", QString::fromStdString(configLocked->getOrderListHeaderFont())).toString().toStdString()); configLocked->setOrderListHeaderFontSize(settings.value("orderListHeaderFontSize", configLocked->getOrderListHeaderFontSize()).toInt()); configLocked->setOrderListRowsFont(settings.value("orderListRowsFont", QString::fromStdString(configLocked->getOrderListRowsFont())).toString().toStdString()); configLocked->setOrderListRowsFontSize(settings.value("orderListRowsFontSize", configLocked->getOrderListRowsFontSize()).toInt()); settings.endGroup(); return true; } catch (...) { return false; } } BambooTracker-0.3.5/BambooTracker/gui/configuration_handler.hpp000066400000000000000000000006271362177441300246230ustar00rootroot00000000000000#ifndef CONF_HPP #define CONF_HPP #include #include #include "configuration.hpp" class ConfigurationHandler { public: static bool saveConfiguration(std::weak_ptr config); static bool loadConfiguration(std::weak_ptr config); private: ConfigurationHandler(); const static QString organization; const static QString application; }; #endif // CONF_HPP BambooTracker-0.3.5/BambooTracker/gui/effect_description.cpp000066400000000000000000000102431362177441300241040ustar00rootroot00000000000000#include "effect_description.hpp" EffectDescription::EffectDescription() {} const std::unordered_map EffectDescription::details_ = { { EffectType::Arpeggio, { "00xy", QT_TR_NOOP("Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F)") } }, { EffectType::PortamentoUp, { "01xx", QT_TR_NOOP("Portamento up, xx: depth (00-FF)") } }, { EffectType::PortamentoDown, { "02xx", QT_TR_NOOP("Portamento down, xx: depth (00-FF)") } }, { EffectType::TonePortamento, { "03xx", QT_TR_NOOP("Tone portamento, xx: depth (00-FF)") } }, { EffectType::Vibrato, { "04xy", QT_TR_NOOP("Vibrato, x: period (0-F), y: depth (0-F)") } }, { EffectType::Tremolo, { "07xx", QT_TR_NOOP("Tremolo, x: period (0-F), y: depth (0-F)") } }, { EffectType::Pan, { "08xx", QT_TR_NOOP("Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center") } }, { EffectType::VolumeSlide, { "0Axy", QT_TR_NOOP("Volume slide, x: up (0-F), y: down (0-F)") } }, { EffectType::PositionJump, { "0Bxx", QT_TR_NOOP("Jump to beginning of order xx") } }, { EffectType::SongEnd, { "0Cxx", QT_TR_NOOP("End of song") } }, { EffectType::PatternBreak, { "0Dxx", QT_TR_NOOP("Jump to step xx of next order") } }, { EffectType::SpeedTempoChange, { "0Fxx", QT_TR_NOOP("Change speed (xx: 00-1F), change tempo (xx: 20-FF)") } }, { EffectType::NoteDelay, { "0Gxx", QT_TR_NOOP("Note delay, xx: count (00-FF)") } }, { EffectType::AutoEnvelope, { "0Hxy", QT_TR_NOOP("Auto envelope, x: shift amount (0-F), y: shape (0-F)") } }, { EffectType::HardEnvHighPeriod, { "0Ixx", QT_TR_NOOP("Hardware envelope period 1, xx: high byte (00-FF)") } }, { EffectType::HardEnvLowPeriod, { "0Jxx", QT_TR_NOOP("Hardware envelope period 2, xx: low byte (00-FF)") } }, { EffectType::Groove, { "0Oxx", QT_TR_NOOP("Set groove xx") } }, { EffectType::Detune, { "0Pxx", QT_TR_NOOP("Detune, xx: pitch (00-FF)") } }, { EffectType::NoteSlideUp, { "0Qxy", QT_TR_NOOP("Note slide up, x: count (0-F), y: seminote (0-F)") } }, { EffectType::NoteSlideDown, { "0Rxy", QT_TR_NOOP("Note slide down, x: count (0-F), y: seminote (0-F)") } }, { EffectType::NoteCut, { "0Sxx", QT_TR_NOOP("Note cut, xx: count (01-FF)") } }, { EffectType::TransposeDelay, { "0Txy", QT_TR_NOOP("Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F)") } }, { EffectType::ToneNoiseMix, { "0Vxx", QT_TR_NOOP("Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise") } }, { EffectType::MasterVolume, { "0Vxx", QT_TR_NOOP("Master volume, xx: volume (00-3F)") } }, { EffectType::NoisePitch, { "0Wxx", QT_TR_NOOP("Noise pitch, xx: pitch (00-1F)") } }, { EffectType::RegisterAddress0, { "0Xxx", QT_TR_NOOP("Register address bank 0, xx: address (00-6B)") } }, { EffectType::RegisterAddress1, { "0Yxx", QT_TR_NOOP("Register address bank 1, xx: address (00-6B)") } }, { EffectType::RegisterValue, { "0Zxx", QT_TR_NOOP("Register value set, xx: value (00-FF)") } }, { EffectType::ARControl, { "Axyy", QT_TR_NOOP("AR control, x: operator (1-4), yy: attack rate (00-1F)") } }, { EffectType::Brightness, { "B0xx", QT_TR_NOOP("Brightness, xx: relative value (01-FF)") } }, { EffectType::DRControl, { "Dxyy", QT_TR_NOOP("DR control, x: operator (1-4), yy: decay rate (00-1F)") } }, { EffectType::FBControl, { "FBxx", QT_TR_NOOP("FB control, xx: feedback value (00-07)") } }, { EffectType::MLControl, { "MLxy", QT_TR_NOOP("ML control, x: operator (1-4), y: multiple (0-F)") } }, { EffectType::VolumeDelay, { "Mxyy", QT_TR_NOOP("Volume delay, x: count (1-F), yy: volume (00-FF)") } }, { EffectType::RRControl, { "RRxy", QT_TR_NOOP("RR control, x: operator (1-4), y: release rate (0-F)") } }, { EffectType::TLControl, { "Txyy", QT_TR_NOOP("TL control, x: operator (1-4), yy: total level (00-7F)") } }, { EffectType::NoEffect, { "", QT_TR_NOOP("Invalid effect") } } }; QString EffectDescription::getEffectFormat(const EffectType type) { return details_.at(type).format; } QString EffectDescription::getEffectDescription(const EffectType type) { return tr(details_.at(type).desc); } QString EffectDescription::getEffectFormatAndDetailString(const EffectType type) { if (type == EffectType::NoEffect) return tr(details_.at(EffectType::NoEffect).desc); else return details_.at(type).mergedString(); } BambooTracker-0.3.5/BambooTracker/gui/effect_description.hpp000066400000000000000000000012771362177441300241200ustar00rootroot00000000000000#ifndef EFFECTDESCRIPTION_HPP #define EFFECTDESCRIPTION_HPP #include #include #include #include "effect.hpp" #include "enum_hash.hpp" class EffectDescription : public QObject { Q_OBJECT public: static QString getEffectFormat(const EffectType type); static QString getEffectDescription(const EffectType type); static QString getEffectFormatAndDetailString(const EffectType type); private: EffectDescription(); struct EffectDetail { const QString format; const char* desc; QString mergedString() const { return format + " - " + tr(desc); } }; static const std::unordered_map details_; }; #endif // EFFECTDESCRIPTION_HPP BambooTracker-0.3.5/BambooTracker/gui/effect_list_dialog.cpp000066400000000000000000000111551362177441300240560ustar00rootroot00000000000000#include "effect_list_dialog.hpp" #include "ui_effect_list_dialog.h" #include #include "gui/effect_description.hpp" EffectListDialog::EffectListDialog(QWidget *parent) : QDialog(parent), ui(new Ui::EffectListDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui->tableWidget->setColumnWidth(0, 50); ui->tableWidget->setColumnWidth(1, 100); addRow(EffectType::Arpeggio, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::PortamentoUp, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::PortamentoDown, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::TonePortamento, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::Vibrato, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::Tremolo, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::Pan, static_cast(SoundSource::FM) | static_cast(SoundSource::DRUM)); addRow(EffectType::VolumeSlide, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::PositionJump, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::SongEnd, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::PatternBreak, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::SpeedTempoChange, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::NoteDelay, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::AutoEnvelope, static_cast(SoundSource::SSG)); addRow(EffectType::HardEnvHighPeriod, static_cast(SoundSource::SSG)); addRow(EffectType::HardEnvLowPeriod, static_cast(SoundSource::SSG)); addRow(EffectType::Groove, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::Detune, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::NoteSlideUp, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::NoteSlideDown, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::NoteCut, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::TransposeDelay, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG)); addRow(EffectType::ToneNoiseMix, static_cast(SoundSource::SSG)); addRow(EffectType::MasterVolume, static_cast(SoundSource::DRUM)); addRow(EffectType::NoisePitch, static_cast(SoundSource::SSG)); addRow(EffectType::ARControl, static_cast(SoundSource::FM)); addRow(EffectType::Brightness, static_cast(SoundSource::FM)); addRow(EffectType::DRControl, static_cast(SoundSource::FM)); addRow(EffectType::FBControl, static_cast(SoundSource::FM)); addRow(EffectType::VolumeDelay, static_cast(SoundSource::FM) | static_cast(SoundSource::SSG) | static_cast(SoundSource::DRUM)); addRow(EffectType::MLControl, static_cast(SoundSource::FM)); addRow(EffectType::RRControl, static_cast(SoundSource::FM)); addRow(EffectType::TLControl, static_cast(SoundSource::FM)); } EffectListDialog::~EffectListDialog() { delete ui; } void EffectListDialog::addRow(EffectType effect, int flag) { int row = ui->tableWidget->rowCount(); ui->tableWidget->insertRow(row); ui->tableWidget->setItem(row, 0, new QTableWidgetItem(EffectDescription::getEffectFormat(effect))); ui->tableWidget->setRowHeight(row, ui->tableWidget->horizontalHeader()->height()); QString type(""); if (flag & static_cast(SoundSource::FM)) type += "FM"; if (flag & static_cast(SoundSource::SSG)) type = type + (type.isEmpty() ? "" : ", ") + "SSG"; if (flag & static_cast(SoundSource::DRUM)) type = type + (type.isEmpty() ? "" : ", ") + "Drums"; ui->tableWidget->setItem(row, 1, new QTableWidgetItem(type)); ui->tableWidget->setItem(row, 2, new QTableWidgetItem(EffectDescription::getEffectDescription(effect))); } BambooTracker-0.3.5/BambooTracker/gui/effect_list_dialog.hpp000066400000000000000000000006731362177441300240660ustar00rootroot00000000000000#ifndef EFFECT_LIST_DIALOG_HPP #define EFFECT_LIST_DIALOG_HPP #include #include #include "effect.hpp" #include "misc.hpp" namespace Ui { class EffectListDialog; } class EffectListDialog : public QDialog { Q_OBJECT public: explicit EffectListDialog(QWidget *parent = nullptr); ~EffectListDialog(); private: Ui::EffectListDialog *ui; void addRow(EffectType effect, int flag); }; #endif // EFFECT_LIST_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/effect_list_dialog.ui000066400000000000000000000043001362177441300237030ustar00rootroot00000000000000 EffectListDialog 0 0 530 400 Effect list QAbstractItemView::NoEditTriggers 27 true false Effect Track type Description Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() EffectListDialog accept() 248 254 157 274 buttonBox rejected() EffectListDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/event_guard.cpp000066400000000000000000000003271362177441300225520ustar00rootroot00000000000000#include "event_guard.hpp" namespace Ui { EventGuard::EventGuard(bool& isIgnoreEvent) : isIgnoreEvent_(isIgnoreEvent) { isIgnoreEvent_ = true; } EventGuard::~EventGuard() { isIgnoreEvent_ = false; } } BambooTracker-0.3.5/BambooTracker/gui/event_guard.hpp000066400000000000000000000003371362177441300225600ustar00rootroot00000000000000#ifndef EVENT_GUARD_HPP #define EVENT_GUARD_HPP namespace Ui { class EventGuard { public: explicit EventGuard(bool& isIgnoreEvent); ~EventGuard(); private: bool& isIgnoreEvent_; }; } #endif // EVENT_GUARD_HPP BambooTracker-0.3.5/BambooTracker/gui/file_history.cpp000066400000000000000000000011531362177441300227450ustar00rootroot00000000000000#include "file_history.hpp" #include const int FileHistory::HISTORY_SIZE_ = 8; FileHistory::FileHistory() {} void FileHistory::addFile(QString path) { auto it = std::find_if(list_.begin(), list_.end(), [&path](QString& p) { return (path == p); }); if (it != list_.end()) list_.erase(it); list_.push_front(path); if (list_.size() > HISTORY_SIZE_) list_.pop_back(); } void FileHistory::clearHistory() { list_.clear(); } QString FileHistory::at(size_t i) { return list_.at(i); } size_t FileHistory::size() const { return list_.size(); } bool FileHistory::empty() const { return list_.empty(); } BambooTracker-0.3.5/BambooTracker/gui/file_history.hpp000066400000000000000000000005401362177441300227510ustar00rootroot00000000000000#ifndef FILE_HISTORY_HPP #define FILE_HISTORY_HPP #include #include class FileHistory { public: FileHistory(); void addFile(QString path); void clearHistory(); QString at(size_t i); size_t size() const; bool empty() const; private: static const int HISTORY_SIZE_; std::deque list_; }; #endif // FILE_HISTORY_HPP BambooTracker-0.3.5/BambooTracker/gui/file_history_handler.cpp000066400000000000000000000022771362177441300244520ustar00rootroot00000000000000#include "file_history_handler.hpp" #include // config path (*nix): ~/.config//.ini const QString FileHistoryHandler::ORGANIZATION_ = "BambooTracker"; const QString FileHistoryHandler::FILE_ = "FileHistory"; FileHistoryHandler::FileHistoryHandler() {} bool FileHistoryHandler::saveFileHistory(std::weak_ptr history) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ORGANIZATION_, FILE_); settings.beginWriteArray("fileHistory"); int n = 0; for (size_t i = 0; i < history.lock()->size(); ++i) { settings.setArrayIndex(n++); settings.setValue("path", history.lock()->at(i)); } settings.endArray(); return true; } catch (...) { return false; } } bool FileHistoryHandler::loadFileHistory(std::weak_ptr history) { try { QSettings settings(QSettings::IniFormat, QSettings::UserScope, ORGANIZATION_, FILE_); int size = settings.beginReadArray("fileHistory"); history.lock()->clearHistory(); for (int i = size - 1; 0 <= i; --i) { settings.setArrayIndex(i); history.lock()->addFile(settings.value("path").toString()); } settings.endArray(); return true; } catch (...) { return false; } } BambooTracker-0.3.5/BambooTracker/gui/file_history_handler.hpp000066400000000000000000000006671362177441300244600ustar00rootroot00000000000000#ifndef FILE_HISTORY_HANDLER_HPP #define FILE_HISTORY_HANDLER_HPP #include #include #include "file_history.hpp" class FileHistoryHandler { public: static bool saveFileHistory(std::weak_ptr history); static bool loadFileHistory(std::weak_ptr history); private: const static QString ORGANIZATION_; const static QString FILE_; FileHistoryHandler(); }; #endif // FILE_HISTORY_HANDLER_HPP BambooTracker-0.3.5/BambooTracker/gui/fm_envelope_set_edit_dialog.cpp000066400000000000000000000163251362177441300257520ustar00rootroot00000000000000#include "fm_envelope_set_edit_dialog.hpp" #include "ui_fm_envelope_set_edit_dialog.h" #include FMEnvelopeSetEditDialog::FMEnvelopeSetEditDialog(std::vector set, QWidget *parent) : QDialog(parent), ui(new Ui::FMEnvelopeSetEditDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui->treeWidget->setColumnCount(2); ui->treeWidget->setHeaderLabels({ tr("Number"), tr("Type") }); for (size_t i = 0; i < set.size(); ++i) { insertRow(static_cast(i), set.at(i)); } } FMEnvelopeSetEditDialog::~FMEnvelopeSetEditDialog() { delete ui; } std::vector FMEnvelopeSetEditDialog::getSet() { std::vector set; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { set.push_back(static_cast( qobject_cast(ui->treeWidget->itemWidget( ui->treeWidget->topLevelItem(i), 1))->currentData().toInt())); } return set; } void FMEnvelopeSetEditDialog::swapset(int aboveRow, int belowRow) { auto* tree = ui->treeWidget; QComboBox* belowBox = makeCombobox(); belowBox->setCurrentIndex(qobject_cast(tree->itemWidget(tree->topLevelItem(belowRow), 1))->currentIndex()); QTreeWidgetItem* below = tree->takeTopLevelItem(belowRow); if (tree->topLevelItemCount() > 2) { QComboBox* aboveBox = makeCombobox(); aboveBox->setCurrentIndex(qobject_cast(tree->itemWidget(tree->topLevelItem(aboveRow), 1))->currentIndex()); QTreeWidgetItem* above = tree->takeTopLevelItem(aboveRow); tree->insertTopLevelItem(aboveRow, below); tree->insertTopLevelItem(belowRow, above); tree->setItemWidget(below, 1, belowBox); tree->setItemWidget(above, 1, aboveBox); } else { tree->insertTopLevelItem(aboveRow, below); tree->setItemWidget(below, 1, belowBox); } if (!aboveRow || !belowRow) alignTreeOn1stItemChanged(); // Dummy set and delete to align for (int i = aboveRow; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } void FMEnvelopeSetEditDialog::insertRow(int row, FMEnvelopeTextType type) { if (row == -1) row = 0; auto item = new QTreeWidgetItem(); item->setText(0, QString::number(row)); QComboBox* box = makeCombobox(); for (int i = 0; i < box->count(); ++i) { if (static_cast(box->itemData(i).toInt()) == type) { box->setCurrentIndex(i); break; } } ui->treeWidget->insertTopLevelItem(row, item); ui->treeWidget->setItemWidget(item, 1, box); if (!row) alignTreeOn1stItemChanged(); // Dummy set and delete to align for (int i = row + 1; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } QComboBox* FMEnvelopeSetEditDialog::makeCombobox() { auto box = new QComboBox(); box->addItem(tr("Skip"), static_cast(FMEnvelopeTextType::Skip)); box->addItem("AL", static_cast(FMEnvelopeTextType::AL)); box->addItem("FB", static_cast(FMEnvelopeTextType::FB)); box->addItem("AR1", static_cast(FMEnvelopeTextType::AR1)); box->addItem("DR1", static_cast(FMEnvelopeTextType::DR1)); box->addItem("SR1", static_cast(FMEnvelopeTextType::SR1)); box->addItem("RR1", static_cast(FMEnvelopeTextType::RR1)); box->addItem("SL1", static_cast(FMEnvelopeTextType::SL1)); box->addItem("TL1", static_cast(FMEnvelopeTextType::TL1)); box->addItem("KS1", static_cast(FMEnvelopeTextType::KS1)); box->addItem("ML1", static_cast(FMEnvelopeTextType::ML1)); box->addItem("DT1", static_cast(FMEnvelopeTextType::DT1)); box->addItem("AR2", static_cast(FMEnvelopeTextType::AR2)); box->addItem("DR2", static_cast(FMEnvelopeTextType::DR2)); box->addItem("SR2", static_cast(FMEnvelopeTextType::SR2)); box->addItem("RR2", static_cast(FMEnvelopeTextType::RR2)); box->addItem("SL2", static_cast(FMEnvelopeTextType::SL2)); box->addItem("TL2", static_cast(FMEnvelopeTextType::TL2)); box->addItem("KS2", static_cast(FMEnvelopeTextType::KS2)); box->addItem("ML2", static_cast(FMEnvelopeTextType::ML2)); box->addItem("DT2", static_cast(FMEnvelopeTextType::DT2)); box->addItem("AR3", static_cast(FMEnvelopeTextType::AR3)); box->addItem("DR3", static_cast(FMEnvelopeTextType::DR3)); box->addItem("SR3", static_cast(FMEnvelopeTextType::SR3)); box->addItem("RR3", static_cast(FMEnvelopeTextType::RR3)); box->addItem("SL3", static_cast(FMEnvelopeTextType::SL3)); box->addItem("TL3", static_cast(FMEnvelopeTextType::TL3)); box->addItem("KS3", static_cast(FMEnvelopeTextType::KS3)); box->addItem("ML3", static_cast(FMEnvelopeTextType::ML3)); box->addItem("DT3", static_cast(FMEnvelopeTextType::DT3)); box->addItem("AR4", static_cast(FMEnvelopeTextType::AR4)); box->addItem("DR4", static_cast(FMEnvelopeTextType::DR4)); box->addItem("SR4", static_cast(FMEnvelopeTextType::SR4)); box->addItem("RR4", static_cast(FMEnvelopeTextType::RR4)); box->addItem("SL4", static_cast(FMEnvelopeTextType::SL4)); box->addItem("TL4", static_cast(FMEnvelopeTextType::TL4)); box->addItem("KS4", static_cast(FMEnvelopeTextType::KS4)); box->addItem("ML4", static_cast(FMEnvelopeTextType::ML4)); box->addItem("DT4", static_cast(FMEnvelopeTextType::DT4)); return box; } /// Dummy set and delete to align void FMEnvelopeSetEditDialog::alignTreeOn1stItemChanged() { auto tmp = new QTreeWidgetItem(); ui->treeWidget->insertTopLevelItem(1, tmp); delete ui->treeWidget->takeTopLevelItem(1); } void FMEnvelopeSetEditDialog::on_upToolButton_clicked() { int curRow = ui->treeWidget->currentIndex().row(); if (!curRow) return; swapset(curRow - 1, curRow); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(curRow - 1)); } void FMEnvelopeSetEditDialog::on_downToolButton_clicked() { int curRow = ui->treeWidget->currentIndex().row(); if (curRow == ui->treeWidget->topLevelItemCount() - 1) return; swapset(curRow, curRow + 1); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem(curRow + 1)); } void FMEnvelopeSetEditDialog::on_addPushButton_clicked() { int row = ui->treeWidget->currentIndex().row(); insertRow(row, FMEnvelopeTextType::Skip); ui->treeWidget->setCurrentItem(ui->treeWidget->topLevelItem((row == -1) ? 0 : row)); ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } void FMEnvelopeSetEditDialog::on_removePushButton_clicked() { int row = ui->treeWidget->currentIndex().row(); delete ui->treeWidget->takeTopLevelItem(row); for (int i = row; i < ui->treeWidget->topLevelItemCount(); ++i) { ui->treeWidget->topLevelItem(i)->setText(0, QString::number(i)); } if (!ui->treeWidget->topLevelItemCount()) { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } } void FMEnvelopeSetEditDialog::on_treeWidget_itemSelectionChanged() { if (ui->treeWidget->currentIndex().row() == -1) { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } else { ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } } BambooTracker-0.3.5/BambooTracker/gui/fm_envelope_set_edit_dialog.hpp000066400000000000000000000016001362177441300257450ustar00rootroot00000000000000#ifndef FM_ENVELOPE_SET_EDIT_DIALOG_HPP #define FM_ENVELOPE_SET_EDIT_DIALOG_HPP #include #include #include #include "misc.hpp" namespace Ui { class FMEnvelopeSetEditDialog; } class FMEnvelopeSetEditDialog : public QDialog { Q_OBJECT public: explicit FMEnvelopeSetEditDialog(std::vector set, QWidget *parent = nullptr); ~FMEnvelopeSetEditDialog(); std::vector getSet(); private slots: void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_addPushButton_clicked(); void on_removePushButton_clicked(); void on_treeWidget_itemSelectionChanged(); private: Ui::FMEnvelopeSetEditDialog *ui; void swapset(int aboveRow, int belowRow); void insertRow(int row, FMEnvelopeTextType type); QComboBox* makeCombobox(); void alignTreeOn1stItemChanged(); }; #endif // FM_ENVELOPE_SET_EDIT_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/fm_envelope_set_edit_dialog.ui000066400000000000000000000101511362177441300255740ustar00rootroot00000000000000 FMEnvelopeSetEditDialog 0 0 400 300 Edit FM envelope set false 0 0 Qt::DownArrow Add 0 0 Set digit types in the order of appearence. false Remove false 0 0 Qt::UpArrow Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Qt::Vertical 20 40 true treeWidget upToolButton downToolButton addPushButton removePushButton buttonBox accepted() FMEnvelopeSetEditDialog accept() 248 254 157 274 buttonBox rejected() FMEnvelopeSetEditDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/groove_settings_dialog.cpp000066400000000000000000000152511362177441300250110ustar00rootroot00000000000000#include "groove_settings_dialog.hpp" #include "ui_groove_settings_dialog.h" #include #include #include #include #include #include GrooveSettingsDialog::GrooveSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::GrooveSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui->grooveListWidget->setSelectionMode(QAbstractItemView::SingleSelection); } GrooveSettingsDialog::~GrooveSettingsDialog() { delete ui; } void GrooveSettingsDialog::setGrooveSquences(std::vector> seqs) { seqs_ = seqs; for (size_t i = 0; i < seqs_.size(); ++i) { auto text = QString("%1: ").arg(i, 2, 16, QChar('0')).toUpper(); for (auto& g : seqs_[i]) { text = text + QString::number(g) + " "; } ui->grooveListWidget->addItem(text); } ui->grooveListWidget->setCurrentRow(0); ui->removeButton->setEnabled(ui->grooveListWidget->count() > 1); } std::vector> GrooveSettingsDialog::getGrooveSequences() { return seqs_; } void GrooveSettingsDialog::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Return: case Qt::Key_Enter: // Prevent dialog from closing when it finished line edit break; default: QDialog::keyPressEvent(event); break; } } void GrooveSettingsDialog::on_addButton_clicked() { ui->grooveListWidget->addItem(QString("%1: 6 6").arg(seqs_.size(), 2, 16, QChar('0')).toUpper()); seqs_.push_back({ 6, 6}); ui->removeButton->setEnabled(true); ui->grooveListWidget->setCurrentRow(ui->grooveListWidget->count() - 1); if (ui->grooveListWidget->count() == 128) ui->addButton->setEnabled(false); } void GrooveSettingsDialog::on_removeButton_clicked() { int row = ui->grooveListWidget->currentRow(); if (row == -1) return; seqs_.erase(seqs_.begin() + row); delete ui->grooveListWidget->takeItem(row); for (int i = row; i < ui->grooveListWidget->count(); ++i) { QString text = QString::number(i) + ": "; for (auto& g : seqs_[static_cast(i)]) { text = text + QString::number(g) + " "; } ui->grooveListWidget->item(i)->setText(text); } on_grooveListWidget_currentRowChanged(ui->grooveListWidget->currentRow()); ui->addButton->setEnabled(true); if (ui->grooveListWidget->count() == 1) ui->removeButton->setEnabled(false); } void GrooveSettingsDialog::on_lineEdit_editingFinished() { int row = ui->grooveListWidget->currentRow(); if (row == -1) return; std::vector seq; QString text = ui->lineEdit->text(); while (!text.isEmpty()) { QRegularExpressionMatch m = QRegularExpression("^(\\d+)").match(text); if (m.hasMatch()) { seq.push_back(m.captured(1).toInt()); text.remove(QRegularExpression("^\\d+")); continue; } m = QRegularExpression("^ +").match(text); if (m.hasMatch()) { text.remove(QRegularExpression("^ +")); continue; } return; } if (seq.empty()) return; seqs_.at(static_cast(row)) = std::move(seq); changeSequence(row); } void GrooveSettingsDialog::on_grooveListWidget_currentRowChanged(int currentRow) { if (currentRow > -1) updateSequence(static_cast(currentRow)); } void GrooveSettingsDialog::changeSequence(int seqNum) { QString text = updateSequence(static_cast(seqNum)); ui->grooveListWidget->item(seqNum)->setText(QString("%1: ").arg(seqNum, 2, 16, QChar('0')).toUpper() + text); } QString GrooveSettingsDialog::updateSequence(size_t seqNum) { ui->seqListWidget->clear(); QString text; auto& seq = seqs_.at(seqNum); for (size_t i = 0; i < seq.size(); ++i) { ui->seqListWidget->addItem(QString("%1: %2").arg(i, 2, 16, QChar('0')).toUpper().arg(seq[i])); text = text + QString::number(seq[i]) + " "; } ui->seqListWidget->setCurrentRow(0); ui->lineEdit->setText(text); double speed = std::accumulate(seq.begin(), seq.end(), 0) / static_cast(seq.size()); ui->speedLabel->setText(tr("Speed: %1").arg(QString::number(speed, 'f', 3))); return text; } void GrooveSettingsDialog::on_upToolButton_clicked() { int curRow = ui->seqListWidget->currentRow(); if (!curRow) return; swapSequenceItem(static_cast(ui->grooveListWidget->currentRow()), curRow - 1, curRow); ui->seqListWidget->setCurrentRow(curRow - 1); } void GrooveSettingsDialog::on_downToolButton_clicked() { int curRow = ui->seqListWidget->currentRow(); if (curRow == ui->seqListWidget->count() - 1) return; swapSequenceItem(static_cast(ui->grooveListWidget->currentRow()), curRow, curRow + 1); ui->seqListWidget->setCurrentRow(curRow + 1); } void GrooveSettingsDialog::swapSequenceItem(size_t seqNum, int index1, int index2) { std::iter_swap(seqs_.at(seqNum).begin() + index1, seqs_.at(seqNum).begin() + index2); changeSequence(static_cast(seqNum)); } void GrooveSettingsDialog::on_expandPushButton_clicked() { size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (std::find(ref.begin(), ref.end(), 1) != ref.end()) return; std::vector seq; for (auto v : ref) { int tmp = v / 2; seq.push_back(v - tmp); seq.push_back(tmp); } seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_shrinkPushButton_clicked() { size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (ref.size() % 2) return; std::vector seq; for (auto it = ref.begin(); it != ref.end(); it += 2) seq.push_back(*it + *(it + 1)); seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_genPushButton_clicked() { int num = ui->numeratorSpinBox->value(); int denom = ui->denominatorSpinBox->value(); if (num < denom) return; std::vector seq(static_cast(denom)); for (int i = 0; i < num * denom; i += num) seq.at(static_cast(denom - i / num - 1)) = (i + num) / denom - i / denom; seqs_.at(static_cast(ui->grooveListWidget->currentRow())) = std::move(seq); changeSequence(ui->grooveListWidget->currentRow()); } void GrooveSettingsDialog::on_padPushButton_clicked() { int pad = ui->padSpinBox->value(); size_t id = static_cast(ui->grooveListWidget->currentRow()); auto& ref = seqs_[id]; if (std::any_of(ref.begin(), ref.end(), [pad](int x) {return (x <= pad); })) return; std::vector seq; for (auto v : ref) { seq.push_back(v - pad); seq.push_back(pad); } seqs_.at(id) = std::move(seq); changeSequence(static_cast(id)); } void GrooveSettingsDialog::on_copyPushButton_clicked() { auto& seq = seqs_[static_cast(ui->grooveListWidget->currentRow())]; auto text = QString("PATTERN_COPY:3,2,%1,").arg(seq.size()); for (auto v : seq) text += QString("0F,%1,").arg(v); QApplication::clipboard()->setText(text); } BambooTracker-0.3.5/BambooTracker/gui/groove_settings_dialog.hpp000066400000000000000000000022461362177441300250160ustar00rootroot00000000000000#ifndef GROOVE_SETTINGS_DIALOG_HPP #define GROOVE_SETTINGS_DIALOG_HPP #include #include #include namespace Ui { class GrooveSettingsDialog; } class GrooveSettingsDialog : public QDialog { Q_OBJECT public: explicit GrooveSettingsDialog(QWidget *parent = nullptr); ~GrooveSettingsDialog() override; void setGrooveSquences(std::vector> seqs); std::vector> getGrooveSequences(); protected: void keyPressEvent(QKeyEvent* event) override; private slots: void on_addButton_clicked(); void on_removeButton_clicked(); void on_lineEdit_editingFinished(); void on_grooveListWidget_currentRowChanged(int currentRow); void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_expandPushButton_clicked(); void on_shrinkPushButton_clicked(); void on_genPushButton_clicked(); void on_padPushButton_clicked(); void on_copyPushButton_clicked(); private: Ui::GrooveSettingsDialog *ui; std::vector> seqs_; void changeSequence(int seqNum); QString updateSequence(size_t seqNum); void swapSequenceItem(size_t seqNum, int index1, int index2); }; #endif // GROOVE_SETTINGS_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/groove_settings_dialog.ui000066400000000000000000000166411362177441300246500ustar00rootroot00000000000000 GrooveSettingsDialog 0 0 400 260 Groove Settings false 0 0 Remove 0 0 Add Speed: xx Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Qt::Horizontal Sequence Copy Fxx 0 0 Qt::UpArrow Expand 3 1 100 12 0 0 / 1 100 2 Generate 0 0 Qt::DownArrow Shrink 1 Pad grooveListWidget seqListWidget upToolButton downToolButton expandPushButton shrinkPushButton genPushButton numeratorSpinBox denominatorSpinBox padPushButton padSpinBox copyPushButton lineEdit addButton removeButton buttonBox accepted() GrooveSettingsDialog accept() 248 254 157 274 buttonBox rejected() GrooveSettingsDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/000077500000000000000000000000001362177441300233175ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/arpeggio_macro_editor.cpp000066400000000000000000000054071362177441300303550ustar00rootroot00000000000000#include "arpeggio_macro_editor.hpp" #include const QString ArpeggioMacroEditor::TONE_LABS_[96] = { "C-0", "C#0", "D-0", "D#0", "E-0", "F-0", "F#0", "G-0", "G#0", "A-0", "A#0", "B-0", "C-1", "C#1", "D-1", "D#1", "E-1", "F-1", "F#1", "G-1", "G#1", "A-1", "A#1", "B-1", "C-2", "C#2", "D-2", "D#2", "E-2", "F-2", "F#2", "G-2", "G#2", "A-2", "A#2", "B-2", "C-3", "C#3", "D-3", "D#3", "E-3", "F-3", "F#3", "G-3", "G#3", "A-3", "A#3", "B-3", "C-4", "C#4", "D-4", "D#4", "E-4", "F-4", "F#4", "G-4", "G#4", "A-4", "A#4", "B-4", "C-5", "C#5", "D-5", "D#5", "E-5", "F-5", "F#5", "G-5", "G#5", "A-5", "A#5", "B-5", "C-6", "C#6", "D-6", "D#6", "E-6", "F-6", "F#6", "G-6", "G#6", "A-6", "A#6", "B-6", "C-7", "C#7", "D-7", "D#7", "E-7", "F-7", "F#7", "G-7", "G#7", "A-7", "A#7", "B-7" }; ArpeggioMacroEditor::ArpeggioMacroEditor(QWidget* parent) : VisualizedInstrumentMacroEditor(parent) { setMaximumDisplayedRowCount(15); setDefaultRow(48); setLabelDiaplayMode(true); for (int i = 0; i < 96; ++i) { AddRow(QString::asprintf("%+d", i - 48), false); } autoFitLabelWidth(); setUpperRow(55); setMMLDisplay0As(-48); setSequenceType(SequenceType::AbsoluteSequence); } ArpeggioMacroEditor::~ArpeggioMacroEditor() {} QString ArpeggioMacroEditor::convertSequenceDataUnitToMML(Column col) { if (type_ == SequenceType::FixedSequence) return TONE_LABS_[col.row]; else return VisualizedInstrumentMacroEditor::convertSequenceDataUnitToMML(col); } bool ArpeggioMacroEditor::interpretDataInMML(QString &text, int &cnt, std::vector &column) { if (type_ == SequenceType::FixedSequence) { QRegularExpressionMatch m = QRegularExpression("^([A-G][-#])([0-7])").match(text); if (m.hasMatch()) { QString tone = m.captured(1); int oct = m.captured(2).toInt(); int d = 12 * oct; if (tone.left(1) == "C") { if (tone.right(1) == "-") ; else d += 1; } else if (tone.left(1) == "D") { if (tone.right(1) == "-") d += 2; else d += 3; } else if (tone.left(1) == "E") d +=4; else if (tone.left(1) == "F") { if (tone.right(1) == "-") d += 5; else d += 6; } else if (tone.left(1) == "G") { if (tone.right(1) == "-") d += 7; else d += 8; } else if (tone.left(1) == "A") { if (tone.right(1) == "-") d += 9; else d += 10; } else d += 11; // 'B' column.push_back({ d, -1, "" }); ++cnt; text.remove(QRegularExpression("^[A-G][-#][0-7]")); return true; } return false; } else { return VisualizedInstrumentMacroEditor::interpretDataInMML(text, cnt, column); } } void ArpeggioMacroEditor::updateLabels() { if (type_ == SequenceType::FixedSequence) { for (int i = 0; i < 96; ++i) setLabel(i, TONE_LABS_[i]); } else { for (int i = 0; i < 96; ++i) setLabel(i, QString::asprintf("%+d", i - 48)); } } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/arpeggio_macro_editor.hpp000066400000000000000000000011511362177441300303520ustar00rootroot00000000000000#ifndef ARPEGGIOMACROEDITOR_HPP #define ARPEGGIOMACROEDITOR_HPP #include "visualized_instrument_macro_editor.hpp" #include #include "instrument.hpp" class ArpeggioMacroEditor final : public VisualizedInstrumentMacroEditor { Q_OBJECT public: ArpeggioMacroEditor(QWidget *parent = nullptr); ~ArpeggioMacroEditor() override; protected: QString convertSequenceDataUnitToMML(Column col) override; bool interpretDataInMML(QString &text, int &cnt, std::vector &column) override; private: static const QString TONE_LABS_[96]; void updateLabels() override; }; #endif // ARPEGGIOMACROEDITOR_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/fm_operator_table.cpp000066400000000000000000000313601362177441300275120ustar00rootroot00000000000000#include "fm_operator_table.hpp" #include "ui_fm_operator_table.h" #include #include #include #include #include "gui/event_guard.hpp" FMOperatorTable::FMOperatorTable(QWidget *parent) : QFrame(parent), ui(new Ui::FMOperatorTable), isDTNegative_(false), isIgnoreEvent_(false) { ui->setupUi(this); ui->groupBox->setContextMenuPolicy(Qt::CustomContextMenu); // Init sliders ui->arSlider->setText("AR"); ui->arSlider->setMaximum(31); ui->drSlider->setText("DR"); ui->drSlider->setMaximum(31); ui->srSlider->setText("SR"); ui->srSlider->setMaximum(31); ui->rrSlider->setText("RR"); ui->rrSlider->setMaximum(15); ui->slSlider->setText("SL"); ui->slSlider->setMaximum(15); ui->tlSlider->setText("TL"); ui->tlSlider->setMaximum(127); ui->ksSlider->setText("KS"); ui->ksSlider->setMaximum(3); ui->mlSlider->setText("ML"); ui->mlSlider->setMaximum(15); ui->dtSlider->setText("DT"); ui->dtSlider->setMaximum(7); sliderMap_ = { { Ui::FMOperatorParameter::AR, ui->arSlider }, { Ui::FMOperatorParameter::DR, ui->drSlider }, { Ui::FMOperatorParameter::SR, ui->srSlider }, { Ui::FMOperatorParameter::RR, ui->rrSlider }, { Ui::FMOperatorParameter::SL, ui->slSlider }, { Ui::FMOperatorParameter::TL, ui->tlSlider }, { Ui::FMOperatorParameter::KS, ui->ksSlider }, { Ui::FMOperatorParameter::ML, ui->mlSlider }, { Ui::FMOperatorParameter::DT, ui->dtSlider } }; for (auto& pair : sliderMap_) { if (pair.second == ui->dtSlider) { QObject::connect(pair.second, &LabeledVerticalSlider::valueChanged, this, [&](int value) { if (isDTNegative_) { switch (value) { case -1: value = 5; break; case -2: value = 6; break; case -3: value = 7; break; default: break; } } repaintGraph(); if (!isIgnoreEvent_) emit operatorValueChanged(pair.first, value); }); } else { QObject::connect(pair.second, &LabeledVerticalSlider::valueChanged, this, [&](int value) { repaintGraph(); if (!isIgnoreEvent_) emit operatorValueChanged(pair.first, value); }); } } ui->ssgegSlider->setEnabled(false); ui->ssgegSlider->setText(tr("Type")); ui->ssgegSlider->setMaximum(7); QObject::connect(ui->ssgegSlider, &LabeledVerticalSlider::valueChanged, this, [&](int value) { repaintGraph(); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, value); }); envmap_ = std::make_unique(); ui->envFrame->installEventFilter(this); } FMOperatorTable::~FMOperatorTable() { delete ui; } void FMOperatorTable::setEnvelopeSetNames(std::vector list) { envelopeTypes_ = list; } void FMOperatorTable::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void FMOperatorTable::setOperatorNumber(int n) { number_ = n; ui->groupBox->setTitle(tr("Operator %1").arg(n + 1)); } int FMOperatorTable::operatorNumber() const { return number_; } void FMOperatorTable::setValue(Ui::FMOperatorParameter param, int value) { if (param == Ui::FMOperatorParameter::SSGEG) { if (value == -1) { ui->ssgegCheckBox->setChecked(false); } else { ui->ssgegCheckBox->setChecked(true); ui->ssgegSlider->setValue(value); } } else { if (param == Ui::FMOperatorParameter::DT) { if (isDTNegative_) { switch (value) { case 4: value = 0; break; case 5: value = -1; break; case 6: value = -2; break; case 7: value = -3; break; default: break; } } } sliderMap_.at(param)->setValue(value); } } void FMOperatorTable::setGroupEnabled(bool enabled) { Ui::EventGuard eg(isIgnoreEvent_); ui->groupBox->setChecked(enabled); } void FMOperatorTable::setDTDisplayType(bool useNegative) { if (isDTNegative_ == useNegative) return; Ui::EventGuard eg(isIgnoreEvent_); isDTNegative_ = useNegative; if (useNegative) { int v = ui->dtSlider->value(); switch (v) { case 4: v = 0; break; case 5: v = -1; break; case 6: v = -2; break; case 7: v = -3; break; default: break; } ui->dtSlider->setMinimum(-3); ui->dtSlider->setMaximum(3); ui->dtSlider->setSign(true); ui->dtSlider->setValue(v); } else { int v = ui->dtSlider->value(); switch (v) { case -1: v = 5; break; case -2: v = 6; break; case -3: v = 7; break; default: break; } ui->dtSlider->setMinimum(0); ui->dtSlider->setMaximum(7); ui->dtSlider->setSign(false); ui->dtSlider->setValue(v); } } QString FMOperatorTable::toString() const { auto str = QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10") .arg(QString::number(ui->arSlider->value())) .arg(QString::number(ui->drSlider->value())) .arg(QString::number(ui->srSlider->value())) .arg(QString::number(ui->rrSlider->value())) .arg(QString::number(ui->slSlider->value())) .arg(QString::number(ui->tlSlider->value())) .arg(QString::number(ui->ksSlider->value())) .arg(QString::number(ui->mlSlider->value())) .arg(QString::number(ui->dtSlider->value())) .arg(ui->ssgegCheckBox->isChecked() ? QString::number(ui->ssgegSlider->value()) : "-1"); return str; } bool FMOperatorTable::eventFilter(QObject* obj, QEvent* event) { if (obj == ui->envFrame) { if (event->type() == QEvent::Paint) { QPainter painter(ui->envFrame); painter.eraseRect(ui->envFrame->rect()); painter.drawPixmap(ui->envFrame->rect(), *envmap_.get(), envmap_->rect()); } } return false; } void FMOperatorTable::showEvent(QShowEvent* event) { Q_UNUSED(event) resizeGraph(); repaintGraph(); } void FMOperatorTable::resizeEvent(QResizeEvent* event) { Q_UNUSED(event) resizeGraph(); repaintGraph(); } void FMOperatorTable::resizeGraph() { envmap_ = std::make_unique(ui->envFrame->size()); xr_ = (envmap_->width() - (ENV_LINE_W_ + 1) * 2.) / ENV_W_; yr_ = (envmap_->height() - (ENV_LINE_W_ + 1) * 2.) / ENV_H_; } void FMOperatorTable::repaintGraph() { if (!palette_) return; QPointF p0, p1, p2, p3, p4; const double marginHorizon = 100; const double marginAr = 50; const double marginDr = 150; const double marginSr = 400; if (ui->arSlider->value() && ui->tlSlider->value() < 127) { p1.setY(127 - ui->tlSlider->value()); if (ui->arSlider->value() < 31) { double ar = 127. / (marginAr * (31 - ui->arSlider->value()) / 30.); p1.setX(p1.y() / ar); } if (ui->drSlider->value()) { p2.setY(p1.y() * (1. - ui->slSlider->value() / 15.)); if (ui->drSlider->value() == 31) { p2.setX(p1.x()); if (ui->slSlider->value() == 15) { p3 = p2; } else { if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + ((ui->slSlider->value() == 15) ? 0 : marginHorizon), p2.y() }; } } } else { double dr = 127. / (marginDr * (31 - ui->drSlider->value()) / 30.); p2.setX(p1.x() + (p1.y() - p2.y()) / dr); if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + ((ui->slSlider->value() == 15) ? 0 : marginHorizon), p2.y() }; } } } else { if (ui->slSlider->value()) { p2 = { p1.x() + marginHorizon, p1.y() }; p3 = p2; } else { p2 = p1; if (ui->srSlider->value()) { p3.setY(p2.y() / 2.); if (ui->srSlider->value() == 31) { p3.setX(p2.x()); } else { double sr = 127. / (marginSr * (31 - ui->srSlider->value()) / 30.); p3.setX(p2.x() + p3.y() / 2. / sr); } } else { p3 = { p2.x() + marginHorizon, p2.y() }; } } } if (!ui->rrSlider->value()) { p4 = { ENV_W_, p3.y() }; } else if (ui->rrSlider->value() == 31) { p4.setX(p3.x()); } else { double rr = 127. / (marginAr * (15 - ui->rrSlider->value()) / 14.); p4.setX(p3.x() + p3.y() / rr); } } double envHeight = ui->ssgegCheckBox->isChecked() ? (ENV_H_ - SSGEG_H_) : ENV_H_; double envHrate = envHeight / 127.; p0.setY((127. - p0.y()) * envHrate); p1.setY((127. - p1.y()) * envHrate); p2.setY((127. - p2.y()) * envHrate); p3.setY((127. - p3.y()) * envHrate); p4.setY((127. - p4.y()) * envHrate); envmap_->fill(palette_->instFMEnvBackColor); QPainter painter(envmap_.get()); painter.setPen(QPen(palette_->instFMEnvGridColor, ENV_LINE_T_)); drawLine(painter, p1.x(), 0, p1.x(), envHeight); drawLine(painter, p2.x(), 0, p2.x(), envHeight); drawLine(painter, p3.x(), 0, p3.x(), envHeight); painter.setPen(QPen(palette_->instFMEnvLine1Color, ENV_LINE_W_)); drawLines(painter, { p0, p1, p2, p3 }); painter.setPen(QPen(palette_->instFMEnvLine2Color, ENV_LINE_W_)); drawLine(painter, p3, p4); if (ui->ssgegCheckBox->isChecked()) { const double seph = envHeight + 1; painter.setPen(QPen(palette_->instFMEnvBorderColor, ENV_LINE_T_)); drawLine(painter, 0, seph, ENV_W_, seph); const double toph = seph + 2; const double both = ENV_H_; const double horsec = ENV_W_ / 5.; const double dhorsec = horsec * 2; painter.setPen(QPen(palette_->instFMEnvLine3Color, ENV_LINE_W_)); switch (ui->ssgegSlider->value()) { case 0: { for (int i = 0; i < 5; ++i) { drawLine(painter, horsec * i, both, horsec * i, toph); drawLine(painter, horsec * i, toph, horsec * (i + 1), both); } } break; case 1: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); drawLine(painter, horsec, both, ENV_W_, both); } break; case 2: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); for (int i = 0; i < 2; ++i) { drawLine(painter, horsec + dhorsec * i, both, dhorsec + dhorsec * i, toph); drawLine(painter, dhorsec + dhorsec * i, toph, horsec + dhorsec * (i + 1), both); } } break; case 3: { drawLine(painter, 0, both, 0, toph); drawLine(painter, 0, toph, horsec, both); drawLine(painter, horsec, both, horsec, toph); drawLine(painter, horsec, toph, ENV_W_, toph); } break; case 4: { for (int i = 0; i < 5; ++i) { drawLine(painter, horsec * i, both, horsec * (i + 1), toph); drawLine(painter, horsec * (i + 1), toph, horsec * (i + 1), both); } } break; case 5: { drawLine(painter, 0, both, horsec, toph); drawLine(painter, horsec, toph, ENV_W_, toph); } break; case 6: { for (int i = 0; i < 2; ++i) { drawLine(painter, dhorsec * i, both, horsec + dhorsec * i, toph); drawLine(painter, horsec + dhorsec * i, toph, dhorsec * (i + 1), both); } drawLine(painter, dhorsec * 2, both, ENV_W_, toph); } break; case 7: { drawLine(painter, 0, both, horsec, toph); drawLine(painter, horsec, toph, horsec, both); drawLine(painter, horsec, both, ENV_W_, both); } break; } } ui->envFrame->repaint(); } void FMOperatorTable::on_ssgegCheckBox_stateChanged(int arg1) { Q_UNUSED(arg1) if (ui->ssgegCheckBox->isChecked()) { ui->ssgegSlider->setEnabled(true); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, ui->ssgegSlider->value()); } else { ui->ssgegSlider->setEnabled(false); if (!isIgnoreEvent_) emit operatorValueChanged(Ui::FMOperatorParameter::SSGEG, -1); } repaintGraph(); } void FMOperatorTable::on_groupBox_toggled(bool arg1) { if (!isIgnoreEvent_) emit operatorEnableChanged(arg1); } void FMOperatorTable::on_groupBox_customContextMenuRequested(const QPoint &pos) { QPoint globalPos = ui->groupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copyEnv = menu.addAction(tr("Copy envelope")); QObject::connect(copyEnv, &QAction::triggered, this, [&] { emit copyEnvelopePressed(); }); QAction* pasteEnv = menu.addAction(tr("Paste envelope")); QObject::connect(pasteEnv, &QAction::triggered, this, [&] { emit pasteEnvelopePressed(); }); QMenu* pasteFrom = menu.addMenu(tr("Paste envelope From")); for (size_t i = 0; i < envelopeTypes_.size(); ++i) { QAction* act = pasteFrom->addAction(envelopeTypes_[i]); act->setData(static_cast(i)); } QObject::connect(pasteFrom, &QMenu::triggered, this, [&](QAction* action) { emit pasteEnvelopeFromPressed(action->data().toInt()); }); menu.addSeparator(); QAction* copyOp = menu.addAction(tr("Copy operator")); QObject::connect(copyOp, &QAction::triggered, this, [&] { emit copyOperatorPressed(number_); }); QAction* pasteOp = menu.addAction(tr("Paste operator")); QObject::connect(pasteOp, &QAction::triggered, this, [&] { emit pasteOperatorPressed(number_); }); QClipboard* clipboard = QApplication::clipboard(); pasteEnv->setEnabled(clipboard->text().startsWith("FM_ENVELOPE:")); pasteOp->setEnabled(clipboard->text().startsWith("FM_OPERATOR:")); menu.exec(globalPos); } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/fm_operator_table.hpp000066400000000000000000000053601362177441300275200ustar00rootroot00000000000000#ifndef FM_OPERATOR_TABLE_HPP #define FM_OPERATOR_TABLE_HPP #include #include #include #include #include #include #include #include #include #include #include "gui/labeled_vertical_slider.hpp" #include "gui/color_palette.hpp" #include "enum_hash.hpp" #include "misc.hpp" namespace Ui { class FMOperatorTable; enum class FMOperatorParameter; } class FMOperatorTable : public QFrame { Q_OBJECT public: explicit FMOperatorTable(QWidget *parent = nullptr); ~FMOperatorTable() override; void setEnvelopeSetNames(std::vector list); void setColorPalette(std::shared_ptr palette); void setOperatorNumber(int n); int operatorNumber() const; void setValue(Ui::FMOperatorParameter param, int value); void setGroupEnabled(bool enabled); void setDTDisplayType(bool useNegative); QString toString() const; protected: bool eventFilter(QObject* obj, QEvent* event) override; void showEvent(QShowEvent* event) override; void resizeEvent(QResizeEvent* event) override; signals: void operatorValueChanged(Ui::FMOperatorParameter param, int value); void operatorEnableChanged(bool enable); void copyEnvelopePressed(); void pasteEnvelopePressed(); void pasteEnvelopeFromPressed(int typenum); void copyOperatorPressed(int num); void pasteOperatorPressed(int num); private slots: void on_ssgegCheckBox_stateChanged(int arg1); void on_groupBox_toggled(bool arg1); void on_groupBox_customContextMenuRequested(const QPoint &pos); private: Ui::FMOperatorTable *ui; std::shared_ptr palette_; int number_; std::unordered_map sliderMap_; std::vector envelopeTypes_; bool isDTNegative_; bool isIgnoreEvent_; // Envelope graph std::unique_ptr envmap_; static constexpr int ENV_H_ = 127; static constexpr int ENV_W_ = 200; static constexpr int SSGEG_H_ = 35; static constexpr int ENV_LINE_W_ = 2; static constexpr int ENV_LINE_T_ = 1; double xr_, yr_; void resizeGraph(); void repaintGraph(); inline void drawLine(QPainter& painter, qreal x1, qreal y1, qreal x2, qreal y2) { painter.drawLine(ENV_LINE_W_ + x1 * xr_ + 1, ENV_LINE_W_ + y1 * yr_ + 1, ENV_LINE_W_ + x2 * xr_ + 1, ENV_LINE_W_ + y2 * yr_ + 1); } inline void drawLine(QPainter& painter, const QPointF& p1, const QPointF& p2) { drawLine(painter, p1.x(), p1.y(), p2.x(), p2.y()); } inline void drawLines(QPainter& painter, const QVector& ps) { for (int i = 1; i < ps.size(); ++i) { drawLine(painter, ps[i-1], ps[i]); } } }; namespace Ui { enum class FMOperatorParameter { AR, DR, SR, RR, SL, TL, KS, ML, DT, AM, SSGEG }; } #endif // FM_OPERATOR_TABLE_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/fm_operator_table.ui000066400000000000000000000205661362177441300273530ustar00rootroot00000000000000 FMOperatorTable 0 0 663 241 Frame FMOperatorTable { border: 0; } 3 0 3 0 Operator true 3 3 3 3 3 0 0 0 70 QFrame::StyledPanel QFrame::Raised 0 12 16777215 12 SSGEG Qt::AlignCenter 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 31 0 QFrame::StyledPanel QFrame::Raised 100 0 QFrame::StyledPanel QFrame::Sunken 1 LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_fm_form.cpp000066400000000000000000002320171362177441300313130ustar00rootroot00000000000000#include "instrument_editor_fm_form.hpp" #include "ui_instrument_editor_fm_form.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "gui/event_guard.hpp" #include "gui/jam_layout.hpp" #include "misc.hpp" InstrumentEditorFMForm::InstrumentEditorFMForm(int num, QWidget *parent) : QWidget(parent), ui(new Ui::InstrumentEditorFMForm), instNum_(num), isIgnoreEvent_(false) { ui->setupUi(this); installEventFilter(this); /******************** Envelope editor ********************/ ui->envGroupBox->setContextMenuPolicy(Qt::CustomContextMenu); auto scene = new QGraphicsScene(ui->alGraphicsView); ui->alGraphicsView->setScene(scene); ui->alSlider->setText("AL"); ui->alSlider->setMaximum(7); QObject::connect(ui->alSlider, &LabeledHorizontalSlider::valueChanged, this, [&](int value) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), FMEnvelopeParameter::AL, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } paintAlgorithmDiagram(); resizeAlgorithmDiagram(); }); ui->fbSlider->setText("FB"); ui->fbSlider->setMaximum(7); QObject::connect(ui->fbSlider, &LabeledHorizontalSlider::valueChanged, this, [&](int value) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), FMEnvelopeParameter::FB, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); ui->op1Table->setOperatorNumber(0); QObject::connect(ui->op1Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 0, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op1Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR1; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR1; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR1; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR1; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL1; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL1; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS1; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML1; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT1; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG1; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op1Table, &FMOperatorTable::copyEnvelopePressed, this, &InstrumentEditorFMForm::copyEnvelope); QObject::connect(ui->op1Table, &FMOperatorTable::pasteEnvelopePressed, this, &InstrumentEditorFMForm::pasteEnvelope); QObject::connect(ui->op1Table, &FMOperatorTable::copyOperatorPressed, this, &InstrumentEditorFMForm::copyOperator); QObject::connect(ui->op1Table, &FMOperatorTable::pasteOperatorPressed, this, &InstrumentEditorFMForm::pasteOperator); QObject::connect(ui->op1Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &InstrumentEditorFMForm::pasteEnvelopeFrom); ui->op2Table->setOperatorNumber(1); QObject::connect(ui->op2Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 1, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op2Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR2; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR2; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR2; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR2; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL2; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL2; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS2; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML2; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT2; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG2; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op2Table, &FMOperatorTable::copyEnvelopePressed, this, &InstrumentEditorFMForm::copyEnvelope); QObject::connect(ui->op2Table, &FMOperatorTable::pasteEnvelopePressed, this, &InstrumentEditorFMForm::pasteEnvelope); QObject::connect(ui->op2Table, &FMOperatorTable::copyOperatorPressed, this, &InstrumentEditorFMForm::copyOperator); QObject::connect(ui->op2Table, &FMOperatorTable::pasteOperatorPressed, this, &InstrumentEditorFMForm::pasteOperator); QObject::connect(ui->op2Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &InstrumentEditorFMForm::pasteEnvelopeFrom); ui->op3Table->setOperatorNumber(2); QObject::connect(ui->op3Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 2, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op3Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR3; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR3; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR3; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR3; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL3; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL3; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS3; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML3; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT3; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG3; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op3Table, &FMOperatorTable::copyEnvelopePressed, this, &InstrumentEditorFMForm::copyEnvelope); QObject::connect(ui->op3Table, &FMOperatorTable::pasteEnvelopePressed, this, &InstrumentEditorFMForm::pasteEnvelope); QObject::connect(ui->op3Table, &FMOperatorTable::copyOperatorPressed, this, &InstrumentEditorFMForm::copyOperator); QObject::connect(ui->op3Table, &FMOperatorTable::pasteOperatorPressed, this, &InstrumentEditorFMForm::pasteOperator); QObject::connect(ui->op3Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &InstrumentEditorFMForm::pasteEnvelopeFrom); ui->op4Table->setOperatorNumber(3); QObject::connect(ui->op4Table, &FMOperatorTable::operatorEnableChanged, this, [&](bool enable) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeFMOperatorEnable(ui->envNumSpinBox->value(), 3, enable); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op4Table, &FMOperatorTable::operatorValueChanged, this, [&](Ui::FMOperatorParameter opParam, int value) { if (!isIgnoreEvent_) { FMEnvelopeParameter param; switch (opParam) { case Ui::FMOperatorParameter::AR: param = FMEnvelopeParameter::AR4; break; case Ui::FMOperatorParameter::DR: param = FMEnvelopeParameter::DR4; break; case Ui::FMOperatorParameter::SR: param = FMEnvelopeParameter::SR4; break; case Ui::FMOperatorParameter::RR: param = FMEnvelopeParameter::RR4; break; case Ui::FMOperatorParameter::SL: param = FMEnvelopeParameter::SL4; break; case Ui::FMOperatorParameter::TL: param = FMEnvelopeParameter::TL4; break; case Ui::FMOperatorParameter::KS: param = FMEnvelopeParameter::KS4; break; case Ui::FMOperatorParameter::ML: param = FMEnvelopeParameter::ML4; break; case Ui::FMOperatorParameter::DT: param = FMEnvelopeParameter::DT4; break; case Ui::FMOperatorParameter::SSGEG: param = FMEnvelopeParameter::SSGEG4; break; default: throw std::invalid_argument("Unexpected FMOperatorParameter."); } bt_.lock()->setEnvelopeFMParameter(ui->envNumSpinBox->value(), param, value); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->op4Table, &FMOperatorTable::copyEnvelopePressed, this, &InstrumentEditorFMForm::copyEnvelope); QObject::connect(ui->op4Table, &FMOperatorTable::pasteEnvelopePressed, this, &InstrumentEditorFMForm::pasteEnvelope); QObject::connect(ui->op4Table, &FMOperatorTable::copyOperatorPressed, this, &InstrumentEditorFMForm::copyOperator); QObject::connect(ui->op4Table, &FMOperatorTable::pasteOperatorPressed, this, &InstrumentEditorFMForm::pasteOperator); QObject::connect(ui->op4Table, &FMOperatorTable::pasteEnvelopeFromPressed, this, &InstrumentEditorFMForm::pasteEnvelopeFrom); /******************** LFO editor ********************/ ui->lfoGroupBox->setContextMenuPolicy(Qt::CustomContextMenu); ui->lfoFreqSlider->setText(tr("Freq")); ui->lfoFreqSlider->setMaximum(7); QObject::connect(ui->lfoFreqSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::FREQ, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); ui->pmsSlider->setText("PMS"); ui->pmsSlider->setMaximum(7); QObject::connect(ui->pmsSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::PMS, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); ui->amsSlider->setText("AMS"); ui->amsSlider->setMaximum(3); QObject::connect(ui->amsSlider, &LabeledVerticalSlider::valueChanged, this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AMS, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp1CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM1, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp2CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM2, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp3CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM3, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->amOp4CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::AM4, (state == Qt::Checked) ? 1 : 0); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->lfoStartSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int v) { if (!isIgnoreEvent_) { bt_.lock()->setLFOFMParameter(ui->lfoNumSpinBox->value(), FMLFOParameter::Count, v); emit lfoParameterChanged(ui->lfoNumSpinBox->value(), instNum_); emit modified(); } }); //========== OperatorSequence ==========// ui->opSeqTypeComboBox->addItem("AL", static_cast(FMEnvelopeParameter::AL)); ui->opSeqTypeComboBox->addItem("FB", static_cast(FMEnvelopeParameter::FB)); ui->opSeqTypeComboBox->addItem("AR1", static_cast(FMEnvelopeParameter::AR1)); ui->opSeqTypeComboBox->addItem("DR1", static_cast(FMEnvelopeParameter::DR1)); ui->opSeqTypeComboBox->addItem("SR1", static_cast(FMEnvelopeParameter::SR1)); ui->opSeqTypeComboBox->addItem("RR1", static_cast(FMEnvelopeParameter::RR1)); ui->opSeqTypeComboBox->addItem("SL1", static_cast(FMEnvelopeParameter::SL1)); ui->opSeqTypeComboBox->addItem("TL1", static_cast(FMEnvelopeParameter::TL1)); ui->opSeqTypeComboBox->addItem("KS1", static_cast(FMEnvelopeParameter::KS1)); ui->opSeqTypeComboBox->addItem("ML1", static_cast(FMEnvelopeParameter::ML1)); ui->opSeqTypeComboBox->addItem("DT1", static_cast(FMEnvelopeParameter::DT1)); ui->opSeqTypeComboBox->addItem("AR2", static_cast(FMEnvelopeParameter::AR2)); ui->opSeqTypeComboBox->addItem("DR2", static_cast(FMEnvelopeParameter::DR2)); ui->opSeqTypeComboBox->addItem("SR2", static_cast(FMEnvelopeParameter::SR2)); ui->opSeqTypeComboBox->addItem("RR2", static_cast(FMEnvelopeParameter::RR2)); ui->opSeqTypeComboBox->addItem("SL2", static_cast(FMEnvelopeParameter::SL2)); ui->opSeqTypeComboBox->addItem("TL2", static_cast(FMEnvelopeParameter::TL2)); ui->opSeqTypeComboBox->addItem("KS2", static_cast(FMEnvelopeParameter::KS2)); ui->opSeqTypeComboBox->addItem("ML2", static_cast(FMEnvelopeParameter::ML2)); ui->opSeqTypeComboBox->addItem("DT2", static_cast(FMEnvelopeParameter::DT2)); ui->opSeqTypeComboBox->addItem("AR3", static_cast(FMEnvelopeParameter::AR3)); ui->opSeqTypeComboBox->addItem("DR3", static_cast(FMEnvelopeParameter::DR3)); ui->opSeqTypeComboBox->addItem("SR3", static_cast(FMEnvelopeParameter::SR3)); ui->opSeqTypeComboBox->addItem("RR3", static_cast(FMEnvelopeParameter::RR3)); ui->opSeqTypeComboBox->addItem("SL3", static_cast(FMEnvelopeParameter::SL3)); ui->opSeqTypeComboBox->addItem("TL3", static_cast(FMEnvelopeParameter::TL3)); ui->opSeqTypeComboBox->addItem("KS3", static_cast(FMEnvelopeParameter::KS3)); ui->opSeqTypeComboBox->addItem("ML3", static_cast(FMEnvelopeParameter::ML3)); ui->opSeqTypeComboBox->addItem("DT3", static_cast(FMEnvelopeParameter::DT3)); ui->opSeqTypeComboBox->addItem("AR4", static_cast(FMEnvelopeParameter::AR4)); ui->opSeqTypeComboBox->addItem("DR4", static_cast(FMEnvelopeParameter::DR4)); ui->opSeqTypeComboBox->addItem("SR4", static_cast(FMEnvelopeParameter::SR4)); ui->opSeqTypeComboBox->addItem("RR4", static_cast(FMEnvelopeParameter::RR4)); ui->opSeqTypeComboBox->addItem("SL4", static_cast(FMEnvelopeParameter::SL4)); ui->opSeqTypeComboBox->addItem("TL4", static_cast(FMEnvelopeParameter::TL4)); ui->opSeqTypeComboBox->addItem("KS4", static_cast(FMEnvelopeParameter::KS4)); ui->opSeqTypeComboBox->addItem("ML4", static_cast(FMEnvelopeParameter::ML4)); ui->opSeqTypeComboBox->addItem("DT4", static_cast(FMEnvelopeParameter::DT4)); ui->opSeqEditor->setDefaultRow(0); ui->opSeqEditor->setLabelDiaplayMode(true); setOperatorSequenceEditor(); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->addOperatorSequenceFMSequenceCommand( param, ui->opSeqNumSpinBox->value(), row, ui->opSeqEditor->getSequenceDataAt(col)); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->removeOperatorSequenceFMSequenceCommand(param, ui->opSeqNumSpinBox->value()); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->setOperatorSequenceFMSequenceCommand( param, ui->opSeqNumSpinBox->value(), col, row, ui->opSeqEditor->getSequenceDataAt(col)); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); bt_.lock()->setOperatorSequenceFMLoops( param, ui->opSeqNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->opSeqEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { FMEnvelopeParameter param = getOperatorSequenceParameter(); ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setOperatorSequenceFMRelease(param, ui->opSeqNumSpinBox->value(), t, point); emit operatorSequenceParameterChanged(param, ui->opSeqNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->opSeqTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorFMForm::onOperatorSequenceTypeChanged); //========== Arpeggio ==========// ui->arpOpComboBox->addItem(tr("All"), static_cast(FMOperatorType::All)); ui->arpOpComboBox->addItem("Op1", static_cast(FMOperatorType::Op1)); ui->arpOpComboBox->addItem("Op2", static_cast(FMOperatorType::Op2)); ui->arpOpComboBox->addItem("Op3", static_cast(FMOperatorType::Op3)); ui->arpOpComboBox->addItem("Op4", static_cast(FMOperatorType::Op4)); ui->arpTypeComboBox->addItem(tr("Absolute"), VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence); ui->arpTypeComboBox->addItem(tr("Fixed"), VisualizedInstrumentMacroEditor::SequenceType::FixedSequence); ui->arpTypeComboBox->addItem(tr("Relative"), VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioFMSequenceCommand( ui->arpNumSpinBox->value(), row, ui->arpEditor->getSequenceDataAt(col)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioFMSequenceCommand(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMSequenceCommand( ui->arpNumSpinBox->value(), col, row, ui->arpEditor->getSequenceDataAt(col)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMLoops( ui->arpNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setArpeggioFMRelease(ui->arpNumSpinBox->value(), t, point); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorFMForm::onArpeggioTypeChanged); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpOpComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorFMForm::onArpeggioOperatorChanged); //========== Pitch ==========// ui->ptOpComboBox->addItem(tr("All"), static_cast(FMOperatorType::All)); ui->ptOpComboBox->addItem("Op1", static_cast(FMOperatorType::Op1)); ui->ptOpComboBox->addItem("Op2", static_cast(FMOperatorType::Op2)); ui->ptOpComboBox->addItem("Op3", static_cast(FMOperatorType::Op3)); ui->ptOpComboBox->addItem("Op4", static_cast(FMOperatorType::Op4)); ui->ptEditor->setMaximumDisplayedRowCount(15); ui->ptEditor->setDefaultRow(127); ui->ptEditor->setLabelDiaplayMode(true); for (int i = 0; i < 255; ++i) { ui->ptEditor->AddRow(QString::asprintf("%+d", i - 127), false); } ui->ptEditor->autoFitLabelWidth(); ui->ptEditor->setUpperRow(134); ui->ptEditor->setMMLDisplay0As(-127); ui->ptTypeComboBox->addItem(tr("Absolute"), VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence); ui->ptTypeComboBox->addItem(tr("Relative"), VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->addPitchFMSequenceCommand( ui->ptNumSpinBox->value(), row, ui->ptEditor->getSequenceDataAt(col)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removePitchFMSequenceCommand(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPitchFMSequenceCommand( ui->ptNumSpinBox->value(), col, row, ui->ptEditor->getSequenceDataAt(col)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setPitchFMLoops( ui->ptNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setPitchFMRelease(ui->ptNumSpinBox->value(), t, point); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorFMForm::onPitchTypeChanged); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptOpComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorFMForm::onPitchOperatorChanged); //========== Others ==========// QObject::connect(ui->envResetCheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::All, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp1CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op1, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp2CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op2, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp3CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op3, (state == Qt::Checked)); emit modified(); } }); QObject::connect(ui->envResetOp4CheckBox, &QCheckBox::stateChanged, this, [&](int state) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelopeResetEnabled(instNum_, FMOperatorType::Op4, (state == Qt::Checked)); emit modified(); } }); } InstrumentEditorFMForm::~InstrumentEditorFMForm() { delete ui; } int InstrumentEditorFMForm::getInstrumentNumber() const { return instNum_; } void InstrumentEditorFMForm::setCore(std::weak_ptr core) { bt_ = core; updateInstrumentParameters(); } void InstrumentEditorFMForm::setConfiguration(std::weak_ptr config) { config_ = config; std::vector names; for (auto texts : config.lock()->getFMEnvelopeTexts()) { names.push_back(QString::fromUtf8(texts.name.c_str(), static_cast(texts.name.length()))); } ui->op1Table->setEnvelopeSetNames(names); ui->op2Table->setEnvelopeSetNames(names); ui->op3Table->setEnvelopeSetNames(names); ui->op4Table->setEnvelopeSetNames(names); updateConfigurationForDisplay(); } void InstrumentEditorFMForm::updateConfigurationForDisplay() { ui->op1Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op2Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op3Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); ui->op4Table->setDTDisplayType(config_.lock()->getShowFMDetuneAsSigned()); } void InstrumentEditorFMForm::setColorPalette(std::shared_ptr palette) { palette_ = palette; ui->op1Table->setColorPalette(palette); ui->op2Table->setColorPalette(palette); ui->op3Table->setColorPalette(palette); ui->op4Table->setColorPalette(palette); ui->opSeqEditor->setColorPalette(palette); ui->arpEditor->setColorPalette(palette); ui->ptEditor->setColorPalette(palette); } SequenceType InstrumentEditorFMForm::convertSequenceTypeForData(VisualizedInstrumentMacroEditor::SequenceType type) { switch (type) { case VisualizedInstrumentMacroEditor::SequenceType::NoType: return SequenceType::NO_SEQUENCE_TYPE; case VisualizedInstrumentMacroEditor::SequenceType::FixedSequence: return SequenceType::FIXED_SEQUENCE; case VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence: return SequenceType::ABSOLUTE_SEQUENCE; case VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence: return SequenceType::RELATIVE_SEQUENCE; default: throw std::invalid_argument("Unexpected SequenceType."); } } VisualizedInstrumentMacroEditor::SequenceType InstrumentEditorFMForm::convertSequenceTypeForUI(SequenceType type) { switch (type) { case SequenceType::NO_SEQUENCE_TYPE: return VisualizedInstrumentMacroEditor::SequenceType::NoType; case SequenceType::FIXED_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::FixedSequence; case SequenceType::ABSOLUTE_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence; case SequenceType::RELATIVE_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence; default: throw std::invalid_argument("Unexpected SequenceType."); } } ReleaseType InstrumentEditorFMForm::convertReleaseTypeForData(VisualizedInstrumentMacroEditor::ReleaseType type) { switch (type) { case VisualizedInstrumentMacroEditor::ReleaseType::NO_RELEASE: return ReleaseType::NoRelease; case VisualizedInstrumentMacroEditor::ReleaseType::FIXED_RELEASE: return ReleaseType::FixedRelease; case VisualizedInstrumentMacroEditor::ReleaseType::ABSOLUTE_RELEASE: return ReleaseType::AbsoluteRelease; case VisualizedInstrumentMacroEditor::ReleaseType::RELATIVE_RELEASE: return ReleaseType::RelativeRelease; default: throw std::invalid_argument("Unexpected ReleaseType."); } } VisualizedInstrumentMacroEditor::ReleaseType InstrumentEditorFMForm::convertReleaseTypeForUI(ReleaseType type) { switch (type) { case ReleaseType::NoRelease: return VisualizedInstrumentMacroEditor::ReleaseType::NO_RELEASE; case ReleaseType::FixedRelease: return VisualizedInstrumentMacroEditor::ReleaseType::FIXED_RELEASE; case ReleaseType::AbsoluteRelease: return VisualizedInstrumentMacroEditor::ReleaseType::ABSOLUTE_RELEASE; case ReleaseType::RelativeRelease: return VisualizedInstrumentMacroEditor::ReleaseType::RELATIVE_RELEASE; default: throw std::invalid_argument("Unexpected ReleaseType."); } } void InstrumentEditorFMForm::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); auto name = QString::fromUtf8(instFM->getName().c_str(), static_cast(instFM->getName().length())); setWindowTitle(QString("%1: %2").arg(instNum_, 2, 16, QChar('0')).toUpper().arg(name)); setInstrumentEnvelopeParameters(); setInstrumentLFOParameters(); setInstrumentOperatorSequenceParameters(); setInstrumentArpeggioParameters(); setInstrumentPitchParameters(); setInstrumentEnvelopeResetParameters(); } /********** Events **********/ // MUST DIRECT CONNECTION void InstrumentEditorFMForm::keyPressEvent(QKeyEvent *event) { // For jam key on // Check keys QString seq = QKeySequence(static_cast(event->modifiers()) | event->key()).toString(); if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveUpKey().c_str(), static_cast(config_.lock()->getOctaveUpKey().length()))).toString()) { emit octaveChanged(true); return; } else if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveDownKey().c_str(), static_cast(config_.lock()->getOctaveDownKey().length()))).toString()) { emit octaveChanged(false); return; } // General keys switch (event->key()) { //case Qt::Key_Return: emit playStatusChanged(0); break; case Qt::Key_F5: emit playStatusChanged(1); break; case Qt::Key_F6: emit playStatusChanged(2); break; case Qt::Key_F7: emit playStatusChanged(3); break; case Qt::Key_F8: emit playStatusChanged(-1); break; case Qt::Key_Escape: close(); break; default: if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void InstrumentEditorFMForm::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } void InstrumentEditorFMForm::showEvent(QShowEvent* event) { Q_UNUSED(event) paintAlgorithmDiagram(); resizeAlgorithmDiagram(); } void InstrumentEditorFMForm::resizeEvent(QResizeEvent* event) { Q_UNUSED(event) resizeAlgorithmDiagram(); } //========== Envelope ==========// void InstrumentEditorFMForm::setInstrumentEnvelopeParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->envNumSpinBox->setValue(instFM->getEnvelopeNumber()); onEnvelopeNumberChanged(); ui->alSlider->setValue(instFM->getEnvelopeParameter(FMEnvelopeParameter::AL)); ui->fbSlider->setValue(instFM->getEnvelopeParameter(FMEnvelopeParameter::FB)); ui->op1Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL1)); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL1)); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS1)); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML1)); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT1)); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG1)); ui->op1Table->setGroupEnabled(instFM->getOperatorEnabled(0)); ui->op2Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL2)); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL2)); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS2)); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML2)); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT2)); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG2)); ui->op2Table->setGroupEnabled(instFM->getOperatorEnabled(1)); ui->op3Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL3)); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL3)); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS3)); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML3)); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT3)); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG3)); ui->op3Table->setGroupEnabled(instFM->getOperatorEnabled(2)); ui->op4Table->setValue(Ui::FMOperatorParameter::AR, instFM->getEnvelopeParameter(FMEnvelopeParameter::AR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, instFM->getEnvelopeParameter(FMEnvelopeParameter::DR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, instFM->getEnvelopeParameter(FMEnvelopeParameter::SR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, instFM->getEnvelopeParameter(FMEnvelopeParameter::RR4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, instFM->getEnvelopeParameter(FMEnvelopeParameter::SL4)); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, instFM->getEnvelopeParameter(FMEnvelopeParameter::TL4)); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, instFM->getEnvelopeParameter(FMEnvelopeParameter::KS4)); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, instFM->getEnvelopeParameter(FMEnvelopeParameter::ML4)); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, instFM->getEnvelopeParameter(FMEnvelopeParameter::DT4)); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, instFM->getEnvelopeParameter(FMEnvelopeParameter::SSGEG4)); ui->op4Table->setGroupEnabled(instFM->getOperatorEnabled(3)); } void InstrumentEditorFMForm::setInstrumentEnvelopeParameters(QString data) { QRegularExpression re("^(?\\d+),(?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),\\s*" "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?-?\\d+),?\\s*"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { ui->fbSlider->setValue(match.captured("fb").toInt()); ui->alSlider->setValue(match.captured("al").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt1").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg1").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt2").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg2").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt3").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg3").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt4").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg4").toInt()); } } void InstrumentEditorFMForm::setInstrumentEnvelopeParameters(int envTypeNum, QString data) { QStringList digits; QRegularExpression re(R"((-?\d+))"); auto it = re.globalMatch(data); while (it.hasNext()) { auto match = it.next(); digits.append(match.captured(1)); } std::vector set = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).texts; if (static_cast(set.size()) != digits.size()) { auto name = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).name; QMessageBox::critical(this, tr("Error"), tr("Did not match the clipboard text format with %1.") .arg(QString::fromUtf8(name.c_str(), static_cast(name.length())))); return; } auto rangeCheck = [](int v, int min, int max) -> int { if (v < min || max < v) throw std::out_of_range(""); else return v; }; auto rangeCheckDT = [rangeCheck](int v) -> int { switch (v) { case -3: v = 7; break; case -2: v = 6; break; case -1: v = 5; break; default: break; } return rangeCheck(v, 0, 7); }; try { for (int i = 0; i < digits.size(); ++i) { int d = digits[i].toInt(); switch (set[static_cast(i)]) { case FMEnvelopeTextType::Skip: break; case FMEnvelopeTextType::AL: ui->alSlider->setValue(rangeCheck(d, 0, 7)); break; case FMEnvelopeTextType::FB: ui->fbSlider->setValue(rangeCheck(d, 0, 7)); break; case FMEnvelopeTextType::AR1: ui->op1Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR1: ui->op1Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR1: ui->op1Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR1: ui->op1Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL1: ui->op1Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL1: ui->op1Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS1: ui->op1Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 3)); break; case FMEnvelopeTextType::ML1: ui->op1Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT1: ui->op1Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR2: ui->op2Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR2: ui->op2Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR2: ui->op2Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR2: ui->op2Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL2: ui->op2Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL2: ui->op2Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS2: ui->op2Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 3)); break; case FMEnvelopeTextType::ML2: ui->op2Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT2: ui->op2Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR3: ui->op4Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR3: ui->op3Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR3: ui->op3Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR3: ui->op3Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL3: ui->op3Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL3: ui->op3Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS3: ui->op3Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 4)); break; case FMEnvelopeTextType::ML3: ui->op3Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT3: ui->op3Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; case FMEnvelopeTextType::AR4: ui->op4Table->setValue(Ui::FMOperatorParameter::AR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::DR4: ui->op4Table->setValue(Ui::FMOperatorParameter::DR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::SR4: ui->op4Table->setValue(Ui::FMOperatorParameter::SR, rangeCheck(d, 0, 31)); break; case FMEnvelopeTextType::RR4: ui->op4Table->setValue(Ui::FMOperatorParameter::RR, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::SL4: ui->op4Table->setValue(Ui::FMOperatorParameter::SL, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::TL4: ui->op4Table->setValue(Ui::FMOperatorParameter::TL, rangeCheck(d, 0, 127)); break; case FMEnvelopeTextType::KS4: ui->op4Table->setValue(Ui::FMOperatorParameter::KS, rangeCheck(d, 0, 4)); break; case FMEnvelopeTextType::ML4: ui->op4Table->setValue(Ui::FMOperatorParameter::ML, rangeCheck(d, 0, 15)); break; case FMEnvelopeTextType::DT4: ui->op4Table->setValue(Ui::FMOperatorParameter::DT, rangeCheckDT(d)); break; } } } catch (...) { auto name = config_.lock()->getFMEnvelopeTexts().at(static_cast(envTypeNum)).name; QMessageBox::critical(this, tr("Error"), tr("Did not match the clipboard text format with %1.") .arg(QString::fromUtf8(name.c_str(), static_cast(name.length())))); return; } } void InstrumentEditorFMForm::setInstrumentOperatorParameters(int opNum, QString data) { QRegularExpression re("^(?\\d+),(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?
\\d+),(?-?\\d+)"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { switch (opNum) { case 0: ui->op1Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op1Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 1: ui->op2Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op2Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 2: ui->op3Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op3Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; case 3: ui->op4Table->setValue(Ui::FMOperatorParameter::AR, match.captured("ar").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DR, match.captured("dr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SR, match.captured("sr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::RR, match.captured("rr").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SL, match.captured("sl").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::TL, match.captured("tl").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::KS, match.captured("ks").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::ML, match.captured("ml").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::DT, match.captured("dt").toInt()); ui->op4Table->setValue(Ui::FMOperatorParameter::SSGEG, match.captured("ssgeg").toInt()); break; } } } void InstrumentEditorFMForm::paintAlgorithmDiagram() { if (!palette_) return; ui->alGraphicsView->setBackgroundBrush(QBrush(palette_->instFMAlBackColor)); QGraphicsScene* scene = ui->alGraphicsView->scene(); // 200 * 70 scene->clear(); QPen pen(palette_->instFMAlForeColor); QBrush brush(palette_->instFMAlForeColor); auto one = new QGraphicsSimpleTextItem(); one->setBrush(brush); one->setText("1"); auto two = new QGraphicsSimpleTextItem(); two->setBrush(brush); two->setText("2"); auto three = new QGraphicsSimpleTextItem(); three->setBrush(brush); three->setText("3"); auto four = new QGraphicsSimpleTextItem(); four->setBrush(brush); four->setText("4"); switch (ui->alSlider->value()) { case 0: { scene->addRect(8, -4, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addRect(104, -4, 16, 8, pen, brush); scene->addLine(0, 0, 128, 0, pen); scene->addLine(0, -8, 32, -8, pen); scene->addLine(0, 0, 0, -8, pen); scene->addLine(32, 0, 32, -8, pen); one->setPos(14, 8); two->setPos(46, 8); three->setPos(78, 8); four->setPos(110, 8); break; } case 1: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(0, -8, 35, -8, pen); scene->addLine(24, 8, 35, 8, pen); scene->addLine(0, -16, 29, -16, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(29, -8, 29, -16, pen); scene->addLine(35, -8, 35, 8, pen); scene->addLine(35, 0, 96, 0, pen); one->setPos(-8, -12); two->setPos(-8, 4); three->setPos(46, 8); four->setPos(78, 8); break; } case 2: { scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(32, -8, 67, -8, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(61, -8, 61, -16, pen); scene->addLine(32, -16, 61, -16, pen); scene->addLine(24, 8, 67, 8, pen); scene->addLine(67, -8, 67, 8, pen); scene->addLine(67, 0, 96, 0, pen); one->setPos(24, -12); two->setPos(14, 16); three->setPos(46, 16); four->setPos(78, 8); break; } case 3: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addRect(72, -4, 16, 8, pen, brush); scene->addLine(0, -8, 64, -8, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(0, -16, 32, -16, pen); scene->addLine(56, 8, 64, 8, pen); scene->addLine(64, -8, 64, 8, pen); scene->addLine(64, 0, 96, 0, pen); one->setPos(-8, -12); two->setPos(46, -24); three->setPos(46, 16); four->setPos(78, 8); break; } case 4: { scene->addRect(8, -12, 16, 8, pen, brush); scene->addRect(40, -12, 16, 8, pen, brush); scene->addRect(8, 4, 16, 8, pen, brush); scene->addRect(40, 4, 16, 8, pen, brush); scene->addLine(0, -8, 64, -8, pen); scene->addLine(0, -8, 0, -16, pen); scene->addLine(32, -8, 32, -16, pen); scene->addLine(0, -16, 32, -16, pen); scene->addLine(24, 8, 64, 8, pen); scene->addLine(64, -8, 64, 8, pen); scene->addLine(64, 0, 72, 0, pen); one->setPos(-8, -12); two->setPos(46, -24); three->setPos(14, 16); four->setPos(46, 16); break; } case 5: { scene->addRect(8, -4, 16, 8, pen, brush); scene->addRect(40, -20, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(40, 12, 16, 8, pen, brush); scene->addLine(0, 0, 72, 0, pen); scene->addLine(0, 0, 0, -8, pen); scene->addLine(29, 0, 29, -8, pen); scene->addLine(0, -8, 29, -8, pen); scene->addLine(35, -16, 35, 16, pen); scene->addLine(64, -16, 64, 16, pen); scene->addLine(35, -16, 64, -16, pen); scene->addLine(35, 16, 64, 16, pen); one->setPos(14, 8); two->setPos(76, -20); three->setPos(76, -4); four->setPos(76, 12); break; } case 6: { scene->addRect(8, -20, 16, 8, pen, brush); scene->addRect(40, -20, 16, 8, pen, brush); scene->addRect(40, -4, 16, 8, pen, brush); scene->addRect(40, 12, 16, 8, pen, brush); scene->addLine(0, -16, 64, -16, pen); scene->addLine(0, -16, 0, -24, pen); scene->addLine(32, -16, 32, -24, pen); scene->addLine(0, -24, 32, -24, pen); scene->addLine(56, 0, 72, 0, pen); scene->addLine(56, 16, 64, 16, pen); scene->addLine(64, -16, 64, 16, pen); one->setPos(14, -8); two->setPos(76, -20); three->setPos(76, -4); four->setPos(76, 12); break; } case 7: { scene->addRect(-28, 8, 8, 16, pen, brush); scene->addRect(-12, 8, 8, 16, pen, brush); scene->addRect(4, 8, 8, 16, pen, brush); scene->addRect(20, 8, 8, 16, pen, brush); scene->addLine(-24, 0, -24, 35, pen); scene->addLine(-24, 0, -32, 0, pen); scene->addLine(-24, 29, -32, 29, pen); scene->addLine(-32, 0, -32, 29, pen); scene->addLine(-8, 24, -8, 35, pen); scene->addLine(8, 24, 8, 35, pen); scene->addLine(24, 24, 24, 35, pen); scene->addLine(-24, 35, 24, 35, pen); scene->addLine(0, 35, 0, 40, pen); one->setPos(-26, -12); two->setPos(-10, -12); three->setPos(6, -12); four->setPos(22, -12); break; } } scene->addItem(one); scene->addItem(two); scene->addItem(three); scene->addItem(four); scene->setSceneRect(scene->itemsBoundingRect()); } void InstrumentEditorFMForm::resizeAlgorithmDiagram() { ui->alGraphicsView->fitInView(ui->alGraphicsView->scene()->itemsBoundingRect(), Qt::AspectRatioMode::KeepAspectRatio); } /********** Slots **********/ void InstrumentEditorFMForm::copyEnvelope() { QApplication::clipboard()->setText(QString("FM_ENVELOPE:%1,%2,\n%3,\n%4,\n%5,\n%6,") .arg(QString::number(ui->fbSlider->value())) .arg(QString::number(ui->alSlider->value())) .arg(ui->op1Table->toString()) .arg(ui->op2Table->toString()) .arg(ui->op3Table->toString()) .arg(ui->op4Table->toString())); } void InstrumentEditorFMForm::pasteEnvelope() { QString data = QApplication::clipboard()->text().remove("FM_ENVELOPE:"); setInstrumentEnvelopeParameters(data); } void InstrumentEditorFMForm::pasteEnvelopeFrom(int typenum) { setInstrumentEnvelopeParameters(typenum, QApplication::clipboard()->text()); } void InstrumentEditorFMForm::copyOperator(int opNum) { QString text; switch (opNum) { case 0: text = ui->op1Table->toString(); break; case 1: text = ui->op2Table->toString(); break; case 2: text = ui->op3Table->toString(); break; case 3: text = ui->op4Table->toString(); break; } QApplication::clipboard()->setText(QString("FM_OPERATOR:") + text); } void InstrumentEditorFMForm::pasteOperator(int opNum) { QString data = QApplication::clipboard()->text().remove("FM_OPERATOR:"); setInstrumentOperatorParameters(opNum, data); } void InstrumentEditorFMForm::on_envNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMEnvelope(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void InstrumentEditorFMForm::on_envGroupBox_customContextMenuRequested(const QPoint &pos) { QPoint globalPos = ui->envGroupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copy = menu.addAction(tr("Copy envelope")); QObject::connect(copy, &QAction::triggered, this, &InstrumentEditorFMForm::copyEnvelope); QAction* paste = menu.addAction(tr("Paste envelope")); QObject::connect(paste, &QAction::triggered, this, &InstrumentEditorFMForm::pasteEnvelope); paste->setEnabled(QApplication::clipboard()->text().startsWith("FM_ENVELOPE:")); QMenu* pasteFrom = menu.addMenu(tr("Paste envelope From")); std::vector textsSet = config_.lock()->getFMEnvelopeTexts(); for (size_t i = 0; i < textsSet.size(); ++i) { QAction* act = pasteFrom->addAction( QString::fromUtf8(textsSet[i].name.c_str(), static_cast(textsSet[i].name.length()))); act->setData(static_cast(i)); } QObject::connect(pasteFrom, &QMenu::triggered, this, [&](QAction* action) { pasteEnvelopeFrom(action->data().toInt()); }); menu.exec(globalPos); } void InstrumentEditorFMForm::onEnvelopeParameterChanged(int envNum) { if (ui->envNumSpinBox->value() == envNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentEnvelopeParameters(); } } void InstrumentEditorFMForm::onEnvelopeNumberChanged() { // Change users view std::vector users = bt_.lock()->getEnvelopeFMUsers(ui->envNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->envUsersLineEdit->setText(l.join((","))); } //========== LFO ==========// void InstrumentEditorFMForm::setInstrumentLFOParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->lfoNumSpinBox->setValue(instFM->getLFONumber()); ui->lfoFreqSlider->setValue(instFM->getLFOParameter(FMLFOParameter::FREQ)); ui->pmsSlider->setValue(instFM->getLFOParameter(FMLFOParameter::PMS)); ui->amsSlider->setValue(instFM->getLFOParameter(FMLFOParameter::AMS)); ui->amOp1CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM1)); ui->amOp2CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM2)); ui->amOp3CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM3)); ui->amOp4CheckBox->setChecked(instFM->getLFOParameter(FMLFOParameter::AM4)); ui->lfoStartSpinBox->setValue(instFM->getLFOParameter(FMLFOParameter::Count)); if (instFM->getLFOEnabled()) { ui->lfoGroupBox->setChecked(true); onLFONumberChanged(); } else { ui->lfoGroupBox->setChecked(false); } } void InstrumentEditorFMForm::setInstrumentLFOParameters(QString data) { QRegularExpression re("^(?\\d+),(?\\d+),(?\\d+),(?\\d+)," "(?\\d+),(?\\d+),(?\\d+),(?\\d+)"); QRegularExpressionMatch match = re.match(data); if (match.hasMatch()) { ui->lfoFreqSlider->setValue(match.captured("freq").toInt()); ui->pmsSlider->setValue(match.captured("pms").toInt()); ui->amsSlider->setValue(match.captured("ams").toInt()); ui->lfoStartSpinBox->setValue(match.captured("cnt").toInt()); ui->amOp1CheckBox->setChecked(match.captured("am1").toInt() == 1); ui->amOp2CheckBox->setChecked(match.captured("am2").toInt() == 1); ui->amOp3CheckBox->setChecked(match.captured("am3").toInt() == 1); ui->amOp4CheckBox->setChecked(match.captured("am4").toInt() == 1); } } QString InstrumentEditorFMForm::toLFOString() const { auto str = QString("%1,%2,%3,%4,%5,%6,%7,%8") .arg(QString::number(ui->lfoFreqSlider->value())) .arg(QString::number(ui->pmsSlider->value())) .arg(QString::number(ui->amsSlider->value())) .arg(QString::number(ui->lfoStartSpinBox->value())) .arg(QString::number(ui->amOp1CheckBox->isChecked() ? 1 : 0)) .arg(QString::number(ui->amOp2CheckBox->isChecked() ? 1 : 0)) .arg(QString::number(ui->amOp3CheckBox->isChecked() ? 1 : 0)) .arg(QString::number(ui->amOp4CheckBox->isChecked() ? 1 : 0)); return str; } /********** Slots **********/ void InstrumentEditorFMForm::onLFOParameterChanged(int lfoNum) { if (ui->lfoNumSpinBox->value() == lfoNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentLFOParameters(); } } void InstrumentEditorFMForm::onLFONumberChanged() { // Change users view std::vector users = bt_.lock()->getLFOFMUsers(ui->lfoNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->lfoUsersLineEdit->setText(l.join(",")); } void InstrumentEditorFMForm::on_lfoGroupBox_customContextMenuRequested(const QPoint &pos) { QClipboard* clipboard = QApplication::clipboard(); QPoint globalPos = ui->lfoGroupBox->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* copy = menu.addAction(tr("Copy LFO parameters")); QObject::connect(copy, &QAction::triggered, this, [&, clipboard]() { clipboard->setText("FM_LFO:" + toLFOString()); }); QAction* paste = menu.addAction(tr("Paste LFO parameters")); QObject::connect(paste, &QAction::triggered, this, [&, clipboard]() { QString data = clipboard->text().remove("FM_LFO:"); setInstrumentLFOParameters(data); }); if (!ui->lfoGroupBox->isChecked()) { copy->setEnabled(false); paste->setEnabled(false); } else if (!clipboard->text().startsWith("FM_LFO:")) { paste->setEnabled(false); } menu.exec(globalPos); } void InstrumentEditorFMForm::on_lfoNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMLFO(instNum_, arg1); setInstrumentLFOParameters(); emit lfoNumberChanged(); emit modified(); } onLFONumberChanged(); } void InstrumentEditorFMForm::on_lfoGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMLFOEnabled(instNum_, arg1); setInstrumentLFOParameters(); emit lfoNumberChanged(); emit modified(); } onLFONumberChanged(); } //--- OperatorSequence FMEnvelopeParameter InstrumentEditorFMForm::getOperatorSequenceParameter() const { return static_cast(ui->opSeqTypeComboBox->currentData(Qt::UserRole).toInt()); } void InstrumentEditorFMForm::setInstrumentOperatorSequenceParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMEnvelopeParameter param = getOperatorSequenceParameter(); ui->opSeqNumSpinBox->setValue(instFM->getOperatorSequenceNumber(param)); ui->opSeqEditor->clearData(); setOperatorSequenceEditor(); for (auto& com : instFM->getOperatorSequenceSequence(param)) { ui->opSeqEditor->addSequenceCommand(com.type); } for (auto& l : instFM->getOperatorSequenceLoops(param)) { ui->opSeqEditor->addLoop(l.begin, l.end, l.times); } ui->opSeqEditor->setRelease(convertReleaseTypeForUI(instFM->getOperatorSequenceRelease(param).type), instFM->getOperatorSequenceRelease(param).begin); if (instFM->getOperatorSequenceEnabled(param)) { ui->opSeqEditGroupBox->setChecked(true); onOperatorSequenceNumberChanged(); } else { ui->opSeqEditGroupBox->setChecked(false); } } void InstrumentEditorFMForm::setOperatorSequenceEditor() { ui->opSeqEditor->clearRow(); FMEnvelopeParameter param = getOperatorSequenceParameter(); switch (param) { case FMEnvelopeParameter::AL: case FMEnvelopeParameter::FB: case FMEnvelopeParameter::DT1: case FMEnvelopeParameter::DT2: case FMEnvelopeParameter::DT3: case FMEnvelopeParameter::DT4: ui->opSeqEditor->setMaximumDisplayedRowCount(8); for (int i = 0; i < 8; ++i) { ui->opSeqEditor->AddRow(QString::number(i), false); } ui->opSeqEditor->autoFitLabelWidth(); ui->opSeqEditor->setUpperRow(7); break; case FMEnvelopeParameter::AR1: case FMEnvelopeParameter::AR2: case FMEnvelopeParameter::AR3: case FMEnvelopeParameter::AR4: case FMEnvelopeParameter::DR1: case FMEnvelopeParameter::DR2: case FMEnvelopeParameter::DR3: case FMEnvelopeParameter::DR4: case FMEnvelopeParameter::SR1: case FMEnvelopeParameter::SR2: case FMEnvelopeParameter::SR3: case FMEnvelopeParameter::SR4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 32; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; case FMEnvelopeParameter::RR1: case FMEnvelopeParameter::RR2: case FMEnvelopeParameter::RR3: case FMEnvelopeParameter::RR4: case FMEnvelopeParameter::SL1: case FMEnvelopeParameter::SL2: case FMEnvelopeParameter::SL3: case FMEnvelopeParameter::SL4: case FMEnvelopeParameter::ML1: case FMEnvelopeParameter::ML2: case FMEnvelopeParameter::ML3: case FMEnvelopeParameter::ML4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 16; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; case FMEnvelopeParameter::KS1: case FMEnvelopeParameter::KS2: case FMEnvelopeParameter::KS3: case FMEnvelopeParameter::KS4: ui->opSeqEditor->setMaximumDisplayedRowCount(4); for (int i = 0; i < 4; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(3); break; case FMEnvelopeParameter::TL1: case FMEnvelopeParameter::TL2: case FMEnvelopeParameter::TL3: case FMEnvelopeParameter::TL4: ui->opSeqEditor->setMaximumDisplayedRowCount(16); for (int i = 0; i < 128; ++i) { ui->opSeqEditor->AddRow(QString::number(i)); } ui->opSeqEditor->setUpperRow(15); break; default: break; } } /********** Slots **********/ void InstrumentEditorFMForm::onOperatorSequenceNumberChanged() { // Change users view std::vector users = bt_.lock()->getOperatorSequenceFMUsers(getOperatorSequenceParameter(), ui->opSeqNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->opSeqUsersLineEdit->setText(l.join(",")); } void InstrumentEditorFMForm::onOperatorSequenceParameterChanged(FMEnvelopeParameter param, int tnNum) { if (param == getOperatorSequenceParameter() && ui->opSeqNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentOperatorSequenceParameters(); } } void InstrumentEditorFMForm::onOperatorSequenceTypeChanged(int type) { Q_UNUSED(type) if (!isIgnoreEvent_) setInstrumentOperatorSequenceParameters(); } void InstrumentEditorFMForm::on_opSeqEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMOperatorSequenceEnabled(instNum_, getOperatorSequenceParameter(), arg1); setInstrumentOperatorSequenceParameters(); emit operatorSequenceNumberChanged(); emit modified(); } onOperatorSequenceNumberChanged(); } void InstrumentEditorFMForm::on_opSeqNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMOperatorSequence(instNum_, getOperatorSequenceParameter(), arg1); setInstrumentOperatorSequenceParameters(); emit operatorSequenceNumberChanged(); emit modified(); } onOperatorSequenceNumberChanged(); } //--- Arpeggio FMOperatorType InstrumentEditorFMForm::getArpeggioOperator() const { return static_cast(ui->arpOpComboBox->currentData(Qt::UserRole).toInt()); } void InstrumentEditorFMForm::setInstrumentArpeggioParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMOperatorType param = getArpeggioOperator(); ui->arpNumSpinBox->setValue(instFM->getArpeggioNumber(param)); ui->arpEditor->clearData(); for (auto& com : instFM->getArpeggioSequence(param)) { ui->arpEditor->addSequenceCommand(com.type); } for (auto& l : instFM->getArpeggioLoops(param)) { ui->arpEditor->addLoop(l.begin, l.end, l.times); } ui->arpEditor->setRelease(convertReleaseTypeForUI(instFM->getArpeggioRelease(param).type), instFM->getArpeggioRelease(param).begin); for (int i = 0; i < ui->arpTypeComboBox->count(); ++i) { if (instFM->getArpeggioType(param) == convertSequenceTypeForData( static_cast(ui->arpTypeComboBox->itemData(i).toInt()))) { ui->arpTypeComboBox->setCurrentIndex(i); break; } } if (instFM->getArpeggioEnabled(param)) { ui->arpEditGroupBox->setChecked(true); onArpeggioNumberChanged(); } else { ui->arpEditGroupBox->setChecked(false); } } /********** Slots **********/ void InstrumentEditorFMForm::onArpeggioNumberChanged() { // Change users view std::vector users = bt_.lock()->getArpeggioFMUsers(ui->arpNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->arpUsersLineEdit->setText(l.join(",")); } void InstrumentEditorFMForm::onArpeggioParameterChanged(int tnNum) { if (ui->arpNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentArpeggioParameters(); } } void InstrumentEditorFMForm::onArpeggioOperatorChanged(int op) { Q_UNUSED(op) if (!isIgnoreEvent_) setInstrumentArpeggioParameters(); } void InstrumentEditorFMForm::onArpeggioTypeChanged(int index) { Q_UNUSED(index) auto type = static_cast( ui->arpTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setArpeggioFMType(ui->arpNumSpinBox->value(), convertSequenceTypeForData(type)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } ui->arpEditor->setSequenceType(type); } void InstrumentEditorFMForm::on_arpEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMArpeggioEnabled(instNum_, getArpeggioOperator(), arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } void InstrumentEditorFMForm::on_arpNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMArpeggio(instNum_, getArpeggioOperator(), arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } //--- Pitch FMOperatorType InstrumentEditorFMForm::getPitchOperator() const { return static_cast(ui->ptOpComboBox->currentData(Qt::UserRole).toInt()); } void InstrumentEditorFMForm::setInstrumentPitchParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); FMOperatorType param = getPitchOperator(); ui->ptNumSpinBox->setValue(instFM->getPitchNumber(param)); ui->ptEditor->clearData(); for (auto& com : instFM->getPitchSequence(param)) { ui->ptEditor->addSequenceCommand(com.type); } for (auto& l : instFM->getPitchLoops(param)) { ui->ptEditor->addLoop(l.begin, l.end, l.times); } ui->ptEditor->setRelease(convertReleaseTypeForUI(instFM->getPitchRelease(param).type), instFM->getPitchRelease(param).begin); for (int i = 0; i < ui->ptTypeComboBox->count(); ++i) { if (instFM->getPitchType(param) == convertSequenceTypeForData( static_cast(ui->ptTypeComboBox->itemData(i).toInt()))) { ui->ptTypeComboBox->setCurrentIndex(i); break; } } if (instFM->getPitchEnabled(param)) { ui->ptEditGroupBox->setChecked(true); onPitchNumberChanged(); } else { ui->ptEditGroupBox->setChecked(false); } } /********** Slots **********/ void InstrumentEditorFMForm::onPitchNumberChanged() { // Change users view std::vector users = bt_.lock()->getPitchFMUsers(ui->ptNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->ptUsersLineEdit->setText(l.join(",")); } void InstrumentEditorFMForm::onPitchParameterChanged(int tnNum) { if (ui->ptNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPitchParameters(); } } void InstrumentEditorFMForm::onPitchOperatorChanged(int op) { Q_UNUSED(op) if (!isIgnoreEvent_) setInstrumentPitchParameters(); } void InstrumentEditorFMForm::onPitchTypeChanged(int index) { Q_UNUSED(index) auto type = static_cast(ui->ptTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setPitchFMType(ui->ptNumSpinBox->value(), convertSequenceTypeForData(type)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } ui->ptEditor->setSequenceType(type); } void InstrumentEditorFMForm::on_ptEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPitchEnabled(instNum_, getPitchOperator(), arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } void InstrumentEditorFMForm::on_ptNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentFMPitch(instNum_, getPitchOperator(), arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } //========== Others ==========// void InstrumentEditorFMForm::setInstrumentEnvelopeResetParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instFM = dynamic_cast(inst.get()); ui->envResetCheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::All)); ui->envResetOp1CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1)); ui->envResetOp2CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2)); ui->envResetOp3CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3)); ui->envResetOp4CheckBox->setChecked(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4)); } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_fm_form.hpp000066400000000000000000000116731362177441300313230ustar00rootroot00000000000000#ifndef INSTRUMENT_EDITOR_FM_FORM_HPP #define INSTRUMENT_EDITOR_FM_FORM_HPP #include #include #include #include #include #include "bamboo_tracker.hpp" #include "instrument.hpp" #include "configuration.hpp" #include "jam_manager.hpp" #include "gui/instrument_editor/visualized_instrument_macro_editor.hpp" #include "gui/color_palette.hpp" namespace Ui { class InstrumentEditorFMForm; } class InstrumentEditorFMForm : public QWidget { Q_OBJECT public: InstrumentEditorFMForm(int num, QWidget *parent = nullptr); ~InstrumentEditorFMForm() override; int getInstrumentNumber() const; void setCore(std::weak_ptr core); void setConfiguration(std::weak_ptr config); void updateConfigurationForDisplay(); void setColorPalette(std::shared_ptr palette); signals: void jamKeyOnEvent(JamKey key); void jamKeyOffEvent(JamKey key); void octaveChanged(bool upFlag); void modified(); /// 0: play song /// 1: play from start /// 2: play pattern /// 3: play from cursor /// -1: stop void playStatusChanged(int stat); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void showEvent(QShowEvent* event) override; void resizeEvent(QResizeEvent* event) override; private: Ui::InstrumentEditorFMForm *ui; int instNum_; bool isIgnoreEvent_; std::weak_ptr bt_; std::shared_ptr palette_; std::weak_ptr config_; SequenceType convertSequenceTypeForData(VisualizedInstrumentMacroEditor::SequenceType type); VisualizedInstrumentMacroEditor::SequenceType convertSequenceTypeForUI(SequenceType type); ReleaseType convertReleaseTypeForData(VisualizedInstrumentMacroEditor::ReleaseType type); VisualizedInstrumentMacroEditor::ReleaseType convertReleaseTypeForUI(ReleaseType type); void updateInstrumentParameters(); //========== Envelope ==========// signals: void envelopeNumberChanged(); void envelopeParameterChanged(int envNum, int fromInstNum); public slots: void onEnvelopeParameterChanged(int envNum); void onEnvelopeNumberChanged(); private: void setInstrumentEnvelopeParameters(); void setInstrumentEnvelopeParameters(QString data); void setInstrumentEnvelopeParameters(int envTypeNum, QString data); void setInstrumentOperatorParameters(int opNum, QString data); void paintAlgorithmDiagram(); void resizeAlgorithmDiagram(); private slots: void copyEnvelope(); void pasteEnvelope(); void pasteEnvelopeFrom(int typenum); void copyOperator(int opNum); void pasteOperator(int opNum); void on_envNumSpinBox_valueChanged(int arg1); void on_envGroupBox_customContextMenuRequested(const QPoint &pos); //========== LFO ==========// signals: void lfoNumberChanged(); void lfoParameterChanged(int lfoNum, int fromInstNum); public slots: void onLFOParameterChanged(int lfoNum); void onLFONumberChanged(); private: void setInstrumentLFOParameters(); void setInstrumentLFOParameters(QString data); QString toLFOString() const; private slots: void on_lfoGroupBox_customContextMenuRequested(const QPoint &pos); void on_lfoNumSpinBox_valueChanged(int arg1); void on_lfoGroupBox_toggled(bool arg1); //========== OperatorSequence ==========// public: FMEnvelopeParameter getOperatorSequenceParameter() const; signals: void operatorSequenceNumberChanged(); void operatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum); public slots: void onOperatorSequenceNumberChanged(); void onOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum); private: void setInstrumentOperatorSequenceParameters(); void setOperatorSequenceEditor(); private slots: void onOperatorSequenceTypeChanged(int type); void on_opSeqEditGroupBox_toggled(bool arg1); void on_opSeqNumSpinBox_valueChanged(int arg1); //========== Arpeggio ==========// public: FMOperatorType getArpeggioOperator() const; signals: void arpeggioNumberChanged(); void arpeggioParameterChanged(int arpNum, int fromInstNum); public slots: void onArpeggioNumberChanged(); void onArpeggioParameterChanged(int arpNum); private: void setInstrumentArpeggioParameters(); private slots: void onArpeggioOperatorChanged(int op); void onArpeggioTypeChanged(int index); void on_arpEditGroupBox_toggled(bool arg1); void on_arpNumSpinBox_valueChanged(int arg1); //========== Pitch ==========// public: FMOperatorType getPitchOperator() const; signals: void pitchNumberChanged(); void pitchParameterChanged(int ptNum, int fromInstNum); public slots: void onPitchNumberChanged(); void onPitchParameterChanged(int arpNum); private: void setInstrumentPitchParameters(); private slots: void onPitchOperatorChanged(int op); void onPitchTypeChanged(int index); void on_ptEditGroupBox_toggled(bool arg1); void on_ptNumSpinBox_valueChanged(int arg1); //========== Others ==========// private: void setInstrumentEnvelopeResetParameters(); }; #endif // INSTRUMENT_EDITOR_FM_FORM_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_fm_form.ui000066400000000000000000000735001362177441300311460ustar00rootroot00000000000000 InstrumentEditorFMForm 0 0 570 750 Form 0 0 0 0 QFrame::NoFrame 0 true 0 0 570 750 0 Envelope Envelope QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised false # 127 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Qt::NoFocus true QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised LFO/Operator sequence Operator sequence Operator: Qt::Horizontal 40 20 Sequence true false false # 127 Qt::Horizontal 40 20 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::NoFocus true LFO true false Qt::NoFocus true false # 127 Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Raised Start count: false AM operators Operator 1 Operator 2 Operator 3 Operator 4 Qt::Vertical 20 40 Envelope reset Reset envelope before key on false FM 3ch Operator 1 Operator 2 Operator 3 Operator 4 Qt::Vertical 20 40 Arpeggio/Pitch Pitch Operator: Qt::Horizontal 40 20 Sequence true false Qt::Horizontal 40 20 false # 127 Type: Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::NoFocus true Arpeggio Operator: Qt::Horizontal 40 20 Sequence true false Type: Users: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false # 127 Qt::Horizontal 40 20 Qt::NoFocus true VisualizedInstrumentMacroEditor QWidget
gui/instrument_editor/visualized_instrument_macro_editor.hpp
1
ArpeggioMacroEditor QWidget
gui/instrument_editor/arpeggio_macro_editor.hpp
1
LabeledVerticalSlider QFrame
gui/labeled_vertical_slider.hpp
1
FMOperatorTable QFrame
gui/instrument_editor/fm_operator_table.hpp
1
LabeledHorizontalSlider QFrame
gui/labeled_horizontal_slider.hpp
1
scrollArea tabWidget envNumSpinBox alGraphicsView lfoGroupBox lfoNumSpinBox lfoStartSpinBox amOp1CheckBox amOp2CheckBox amOp3CheckBox amOp4CheckBox envResetCheckBox envResetOp1CheckBox envResetOp2CheckBox envResetOp3CheckBox envResetOp4CheckBox opSeqTypeComboBox opSeqEditGroupBox opSeqNumSpinBox arpOpComboBox arpEditGroupBox arpNumSpinBox arpTypeComboBox ptOpComboBox ptEditGroupBox ptNumSpinBox ptTypeComboBox
BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_ssg_form.cpp000066400000000000000000000771221362177441300315110ustar00rootroot00000000000000#include "instrument_editor_ssg_form.hpp" #include "ui_instrument_editor_ssg_form.h" #include #include #include #include "gui/event_guard.hpp" #include "pitch_converter.hpp" #include "command_sequence.hpp" #include "gui/jam_layout.hpp" #include "misc.hpp" InstrumentEditorSSGForm::InstrumentEditorSSGForm(int num, QWidget *parent) : QWidget(parent), ui(new Ui::InstrumentEditorSSGForm), instNum_(num) { ui->setupUi(this); //========== Wave form ==========// ui->waveEditor->setMaximumDisplayedRowCount(7); ui->waveEditor->setDefaultRow(0); ui->waveEditor->AddRow(tr("Sq"), false); ui->waveEditor->AddRow(tr("Tri"), false); ui->waveEditor->AddRow(tr("Saw"), false); ui->waveEditor->AddRow(tr("InvSaw"), false); ui->waveEditor->AddRow(tr("SMTri"), false); ui->waveEditor->AddRow(tr("SMSaw"), false); ui->waveEditor->AddRow(tr("SMInvSaw"), false); ui->waveEditor->autoFitLabelWidth(); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (isModulatedWaveFormSSG(row)) setWaveFormSequenceColumn(col); // Set square-mask frequency bt_.lock()->addWaveFormSSGSequenceCommand( ui->waveNumSpinBox->value(), row, ui->waveEditor->getSequenceDataAt(col)); emit waveFormParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removeWaveFormSSGSequenceCommand(ui->waveNumSpinBox->value()); emit waveFormParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (isModulatedWaveFormSSG(row)) setWaveFormSequenceColumn(col); // Set square-mask frequency bt_.lock()->setWaveFormSSGSequenceCommand( ui->waveNumSpinBox->value(), col, row, ui->waveEditor->getSequenceDataAt(col)); emit waveFormParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setWaveFormSSGLoops( ui->waveNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit waveFormParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->waveEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setWaveFormSSGRelease(ui->waveNumSpinBox->value(), t, point); emit waveFormParameterChanged(ui->waveNumSpinBox->value(), instNum_); emit modified(); } }); //========== Tone/Noise ==========// QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->addToneNoiseSSGSequenceCommand( ui->tnNumSpinBox->value(), row, ui->tnEditor->getSequenceDataAt(col)); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removeToneNoiseSSGSequenceCommand(ui->tnNumSpinBox->value()); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setToneNoiseSSGSequenceCommand( ui->tnNumSpinBox->value(), col, row, ui->tnEditor->getSequenceDataAt(col)); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setToneNoiseSSGLoops( ui->tnNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->tnEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setToneNoiseSSGRelease(ui->tnNumSpinBox->value(), t, point); emit toneNoiseParameterChanged(ui->tnNumSpinBox->value(), instNum_); emit modified(); } }); //========== Envelope ==========// ui->envEditor->setMaximumDisplayedRowCount(16); ui->envEditor->setDefaultRow(15); for (int i = 0; i < 16; ++i) { ui->envEditor->AddRow(QString::number(i), false); } for (int i = 0; i < 8; ++i) { ui->envEditor->AddRow(tr("HEnv %1").arg(i), false); } ui->envEditor->autoFitLabelWidth(); ui->envEditor->setMultipleReleaseState(true); ui->envEditor->setPermittedReleaseTypes( VisualizedInstrumentMacroEditor::ReleaseType::ABSOLUTE_RELEASE | VisualizedInstrumentMacroEditor::ReleaseType::RELATIVE_RELEASE | VisualizedInstrumentMacroEditor::ReleaseType::FIXED_RELEASE); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (row >= 16) setEnvelopeSequenceColumn(col); // Set hard frequency bt_.lock()->addEnvelopeSSGSequenceCommand( ui->envNumSpinBox->value(), row, ui->envEditor->getSequenceDataAt(col)); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removeEnvelopeSSGSequenceCommand(ui->envNumSpinBox->value()); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { if (row >= 16) setEnvelopeSequenceColumn(col); // Set hard frequency bt_.lock()->setEnvelopeSSGSequenceCommand( ui->envNumSpinBox->value(), col, row, ui->envEditor->getSequenceDataAt(col)); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setEnvelopeSSGLoops( ui->envNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->envEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setEnvelopeSSGRelease(ui->envNumSpinBox->value(), t, point); emit envelopeParameterChanged(ui->envNumSpinBox->value(), instNum_); emit modified(); } }); //========== Arpeggio ==========// ui->arpTypeComboBox->addItem(tr("Absolute"), VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence); ui->arpTypeComboBox->addItem(tr("Fixed"), VisualizedInstrumentMacroEditor::SequenceType::FixedSequence); ui->arpTypeComboBox->addItem(tr("Relative"), VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->addArpeggioSSGSequenceCommand( ui->arpNumSpinBox->value(), row, ui->arpEditor->getSequenceDataAt(col)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removeArpeggioSSGSequenceCommand(ui->arpNumSpinBox->value()); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGSequenceCommand( ui->arpNumSpinBox->value(), col, row, ui->arpEditor->getSequenceDataAt(col)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGLoops( ui->arpNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->arpEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setArpeggioSSGRelease(ui->arpNumSpinBox->value(), t, point); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->arpTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorSSGForm::onArpeggioTypeChanged); //========== Pitch ==========// ui->ptEditor->setMaximumDisplayedRowCount(15); ui->ptEditor->setDefaultRow(127); ui->ptEditor->setLabelDiaplayMode(true); for (int i = 0; i < 255; ++i) { ui->ptEditor->AddRow(QString::asprintf("%+d", i - 127), false); } ui->ptEditor->autoFitLabelWidth(); ui->ptEditor->setUpperRow(134); ui->ptEditor->setMMLDisplay0As(-127); ui->ptTypeComboBox->addItem(tr("Absolute"), VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence); ui->ptTypeComboBox->addItem(tr("Relative"), VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandAdded, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->addPitchSSGSequenceCommand( ui->ptNumSpinBox->value(), row, ui->ptEditor->getSequenceDataAt(col)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandRemoved, this, [&]() { if (!isIgnoreEvent_) { bt_.lock()->removePitchSSGSequenceCommand(ui->ptNumSpinBox->value()); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::sequenceCommandChanged, this, [&](int row, int col) { if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGSequenceCommand( ui->ptNumSpinBox->value(), col, row, ui->ptEditor->getSequenceDataAt(col)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::loopChanged, this, [&](std::vector begins, std::vector ends, std::vector times) { if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGLoops( ui->ptNumSpinBox->value(), std::move(begins), std::move(ends), std::move(times)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); QObject::connect(ui->ptEditor, &VisualizedInstrumentMacroEditor::releaseChanged, this, [&](VisualizedInstrumentMacroEditor::ReleaseType type, int point) { if (!isIgnoreEvent_) { ReleaseType t = convertReleaseTypeForData(type); bt_.lock()->setPitchSSGRelease(ui->ptNumSpinBox->value(), t, point); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->ptTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &InstrumentEditorSSGForm::onPitchTypeChanged); } InstrumentEditorSSGForm::~InstrumentEditorSSGForm() { delete ui; } int InstrumentEditorSSGForm::getInstrumentNumber() const { return instNum_; } void InstrumentEditorSSGForm::setCore(std::weak_ptr core) { bt_ = core; updateInstrumentParameters(); } void InstrumentEditorSSGForm::setConfiguration(std::weak_ptr config) { config_ = config; } void InstrumentEditorSSGForm::setColorPalette(std::shared_ptr palette) { ui->waveEditor->setColorPalette(palette); ui->tnEditor->setColorPalette(palette); ui->envEditor->setColorPalette(palette); ui->arpEditor->setColorPalette(palette); ui->ptEditor->setColorPalette(palette); } SequenceType InstrumentEditorSSGForm::convertSequenceTypeForData(VisualizedInstrumentMacroEditor::SequenceType type) { switch (type) { case VisualizedInstrumentMacroEditor::SequenceType::NoType: return SequenceType::NO_SEQUENCE_TYPE; case VisualizedInstrumentMacroEditor::SequenceType::FixedSequence: return SequenceType::FIXED_SEQUENCE; case VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence: return SequenceType::ABSOLUTE_SEQUENCE; case VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence: return SequenceType::RELATIVE_SEQUENCE; default: throw std::invalid_argument("Unexpected SequenceType."); } } VisualizedInstrumentMacroEditor::SequenceType InstrumentEditorSSGForm::convertSequenceTypeForUI(SequenceType type) { switch (type) { case SequenceType::NO_SEQUENCE_TYPE: return VisualizedInstrumentMacroEditor::SequenceType::NoType; case SequenceType::FIXED_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::FixedSequence; case SequenceType::ABSOLUTE_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::AbsoluteSequence; case SequenceType::RELATIVE_SEQUENCE: return VisualizedInstrumentMacroEditor::SequenceType::RelativeSequence; default: throw std::invalid_argument("Unexpected SequenceType."); } } ReleaseType InstrumentEditorSSGForm::convertReleaseTypeForData(VisualizedInstrumentMacroEditor::ReleaseType type) { switch (type) { case VisualizedInstrumentMacroEditor::ReleaseType::NO_RELEASE: return ReleaseType::NoRelease; case VisualizedInstrumentMacroEditor::ReleaseType::FIXED_RELEASE: return ReleaseType::FixedRelease; case VisualizedInstrumentMacroEditor::ReleaseType::ABSOLUTE_RELEASE: return ReleaseType::AbsoluteRelease; case VisualizedInstrumentMacroEditor::ReleaseType::RELATIVE_RELEASE: return ReleaseType::RelativeRelease; default: throw std::invalid_argument("Unexpected ReleaseType."); } } VisualizedInstrumentMacroEditor::ReleaseType InstrumentEditorSSGForm::convertReleaseTypeForUI(ReleaseType type) { switch (type) { case ReleaseType::NoRelease: return VisualizedInstrumentMacroEditor::ReleaseType::NO_RELEASE; case ReleaseType::FixedRelease: return VisualizedInstrumentMacroEditor::ReleaseType::FIXED_RELEASE; case ReleaseType::AbsoluteRelease: return VisualizedInstrumentMacroEditor::ReleaseType::ABSOLUTE_RELEASE; case ReleaseType::RelativeRelease: return VisualizedInstrumentMacroEditor::ReleaseType::RELATIVE_RELEASE; default: throw std::invalid_argument("Unexpected ReleaseType."); } } void InstrumentEditorSSGForm::updateInstrumentParameters() { Ui::EventGuard eg(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); auto name = QString::fromUtf8(instSSG->getName().c_str(), static_cast(instSSG->getName().length())); setWindowTitle(QString("%1: %2").arg(instNum_, 2, 16, QChar('0')).toUpper().arg(name)); setInstrumentWaveFormParameters(); setInstrumentToneNoiseParameters(); setInstrumentEnvelopeParameters(); setInstrumentArpeggioParameters(); setInstrumentPitchParameters(); } /********** Events **********/ // MUST DIRECT CONNECTION void InstrumentEditorSSGForm::keyPressEvent(QKeyEvent *event) { // For jam key on // Check keys QString seq = QKeySequence(static_cast(event->modifiers()) | event->key()).toString(); if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveUpKey().c_str(), static_cast(config_.lock()->getOctaveUpKey().length()))).toString()) { emit octaveChanged(true); return; } else if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveDownKey().c_str(), static_cast(config_.lock()->getOctaveDownKey().length()))).toString()) { emit octaveChanged(false); return; } // General keys switch (event->key()) { //case Qt::Key_Return: emit playStatusChanged(0); break; case Qt::Key_F5: emit playStatusChanged(1); break; case Qt::Key_F6: emit playStatusChanged(2); break; case Qt::Key_F7: emit playStatusChanged(3); break; case Qt::Key_F8: emit playStatusChanged(-1); break; case Qt::Key_Escape: close(); break; default: if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOnEvent(jk); } catch (std::invalid_argument&) {} } break; } } // MUST DIRECT CONNECTION void InstrumentEditorSSGForm::keyReleaseEvent(QKeyEvent *event) { // For jam key off if (!event->isAutoRepeat()) { Qt::Key qtKey = static_cast(event->key()); try { JamKey jk = getJamKeyFromLayoutMapping(qtKey, config_); emit jamKeyOffEvent(jk); } catch (std::invalid_argument&) {} } } //--- Wave form void InstrumentEditorSSGForm::setInstrumentWaveFormParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->waveNumSpinBox->setValue(instSSG->getWaveFormNumber()); ui->waveEditor->clearData(); for (auto& com : instSSG->getWaveFormSequence()) { QString str(""); if (isModulatedWaveFormSSG(com.type)) { if (CommandSequenceUnit::checkDataType(com.data) == CommandSequenceUnit::RATIO) { auto ratio = CommandSequenceUnit::data2ratio(com.data); str = QString("%1/%2").arg(ratio.first).arg(ratio.second); } else { str = QString::number(com.data); } } ui->waveEditor->addSequenceCommand(com.type, str, com.data); } for (auto& l : instSSG->getWaveFormLoops()) { ui->waveEditor->addLoop(l.begin, l.end, l.times); } ui->waveEditor->setRelease(convertReleaseTypeForUI(instSSG->getWaveFormRelease().type), instSSG->getWaveFormRelease().begin); if (instSSG->getWaveFormEnabled()) { ui->waveEditGroupBox->setChecked(true); onWaveFormNumberChanged(); } else { ui->waveEditGroupBox->setChecked(false); } } void InstrumentEditorSSGForm::setWaveFormSequenceColumn(int col) { auto button = ui->squareMaskButtonGroup->checkedButton(); if (button == ui->squareMaskRawRadioButton) { ui->waveEditor->setText(col, QString::number(ui->squareMaskRawSpinBox->value())); ui->waveEditor->setData(col, ui->squareMaskRawSpinBox->value()); } else { ui->waveEditor->setText(col, QString::number(ui->squareMaskToneSpinBox->value()) + "/" + QString::number(ui->squareMaskMaskSpinBox->value())); ui->waveEditor->setData(col, CommandSequenceUnit::ratio2data( ui->squareMaskToneSpinBox->value(), ui->squareMaskMaskSpinBox->value())); } } /********** Slots **********/ void InstrumentEditorSSGForm::onWaveFormNumberChanged() { // Change users view std::vector users = bt_.lock()->getWaveFormSSGUsers(ui->waveNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->waveUsersLineEdit->setText(l.join(",")); } void InstrumentEditorSSGForm::onWaveFormParameterChanged(int wfNum) { if (ui->waveNumSpinBox->value() == wfNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentWaveFormParameters(); } } void InstrumentEditorSSGForm::on_waveEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGWaveFormEnabled(instNum_, arg1); setInstrumentWaveFormParameters(); emit waveFormNumberChanged(); emit modified(); } onWaveFormNumberChanged(); } void InstrumentEditorSSGForm::on_waveNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGWaveForm(instNum_, arg1); setInstrumentWaveFormParameters(); emit waveFormNumberChanged(); emit modified(); } onWaveFormNumberChanged(); } void InstrumentEditorSSGForm::on_squareMaskRawSpinBox_valueChanged(int arg1) { ui->squareMaskRawSpinBox->setSuffix( QString(" (0x") + QString("%1 | ").arg(arg1, 3, 16, QChar('0')).toUpper() + QString("%1Hz)").arg(arg1 ? QString::number(124800.0 / arg1, 'f', 4) : "-") ); } //--- Tone/Noise void InstrumentEditorSSGForm::setInstrumentToneNoiseParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->tnNumSpinBox->setValue(instSSG->getToneNoiseNumber()); ui->tnEditor->clearData(); for (auto& com : instSSG->getToneNoiseSequence()) { ui->tnEditor->addSequenceCommand(com.type); } for (auto& l : instSSG->getToneNoiseLoops()) { ui->tnEditor->addLoop(l.begin, l.end, l.times); } ui->tnEditor->setRelease(convertReleaseTypeForUI(instSSG->getToneNoiseRelease().type), instSSG->getToneNoiseRelease().begin); if (instSSG->getToneNoiseEnabled()) { ui->tnEditGroupBox->setChecked(true); onToneNoiseNumberChanged(); } else { ui->tnEditGroupBox->setChecked(false); } } /********** Slots **********/ void InstrumentEditorSSGForm::onToneNoiseNumberChanged() { // Change users view std::vector users = bt_.lock()->getToneNoiseSSGUsers(ui->tnNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->tnUsersLineEdit->setText(l.join((","))); } void InstrumentEditorSSGForm::onToneNoiseParameterChanged(int tnNum) { if (ui->tnNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentToneNoiseParameters(); } } void InstrumentEditorSSGForm::on_tnEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGToneNoiseEnabled(instNum_, arg1); setInstrumentToneNoiseParameters(); emit toneNoiseNumberChanged(); emit modified(); } onToneNoiseNumberChanged(); } void InstrumentEditorSSGForm::on_tnNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGToneNoise(instNum_, arg1); setInstrumentToneNoiseParameters(); emit toneNoiseNumberChanged(); emit modified(); } onToneNoiseNumberChanged(); } //--- Envelope void InstrumentEditorSSGForm::setInstrumentEnvelopeParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->envNumSpinBox->setValue(instSSG->getEnvelopeNumber()); ui->envEditor->clearData(); for (auto& com : instSSG->getEnvelopeSequence()) { QString str(""); if (com.type >= 16) { if (CommandSequenceUnit::checkDataType(com.data) == CommandSequenceUnit::RATIO) { auto ratio = CommandSequenceUnit::data2ratio(com.data); str = QString("%1/%2").arg(ratio.first).arg(ratio.second); } else { str = QString::number(com.data); } } ui->envEditor->addSequenceCommand(com.type, str, com.data); } for (auto& l : instSSG->getEnvelopeLoops()) { ui->envEditor->addLoop(l.begin, l.end, l.times); } ui->envEditor->setRelease(convertReleaseTypeForUI(instSSG->getEnvelopeRelease().type), instSSG->getEnvelopeRelease().begin); if (instSSG->getEnvelopeEnabled()) { ui->envEditGroupBox->setChecked(true); onEnvelopeNumberChanged(); } else { ui->envEditGroupBox->setChecked(false); } } void InstrumentEditorSSGForm::setEnvelopeSequenceColumn(int col) { if (ui->hardFreqButtonGroup->checkedButton() == ui->hardFreqRawRadioButton) { ui->envEditor->setText(col, QString::number(ui->hardFreqRawSpinBox->value())); ui->envEditor->setData(col, ui->hardFreqRawSpinBox->value()); } else { ui->envEditor->setText(col, QString::number(ui->hardFreqToneSpinBox->value()) + "/" + QString::number(ui->hardFreqHardSpinBox->value())); ui->envEditor->setData(col, CommandSequenceUnit::ratio2data( ui->hardFreqToneSpinBox->value(), ui->hardFreqHardSpinBox->value())); } } /********** Slots **********/ void InstrumentEditorSSGForm::onEnvelopeNumberChanged() { // Change users view std::vector users = bt_.lock()->getEnvelopeSSGUsers(ui->envNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->envUsersLineEdit->setText(l.join((","))); } void InstrumentEditorSSGForm::onEnvelopeParameterChanged(int envNum) { if (ui->envNumSpinBox->value() == envNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentEnvelopeParameters(); } } void InstrumentEditorSSGForm::on_envEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGEnvelopeEnabled(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void InstrumentEditorSSGForm::on_envNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGEnvelope(instNum_, arg1); setInstrumentEnvelopeParameters(); emit envelopeNumberChanged(); emit modified(); } onEnvelopeNumberChanged(); } void InstrumentEditorSSGForm::on_hardFreqRawSpinBox_valueChanged(int arg1) { ui->hardFreqRawSpinBox->setSuffix( QString(" (0x") + QString("%1 | ").arg(arg1, 4, 16, QChar('0')).toUpper() + QString("%1Hz").arg(arg1 ? QString::number(7800.0 / arg1, 'f', 4) : "-")); } //--- Arpeggio void InstrumentEditorSSGForm::setInstrumentArpeggioParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->arpNumSpinBox->setValue(instSSG->getArpeggioNumber()); ui->arpEditor->clearData(); for (auto& com : instSSG->getArpeggioSequence()) { ui->arpEditor->addSequenceCommand(com.type); } for (auto& l : instSSG->getArpeggioLoops()) { ui->arpEditor->addLoop(l.begin, l.end, l.times); } ui->arpEditor->setRelease(convertReleaseTypeForUI(instSSG->getArpeggioRelease().type), instSSG->getArpeggioRelease().begin); for (int i = 0; i < ui->arpTypeComboBox->count(); ++i) { if (instSSG->getArpeggioType() == convertSequenceTypeForData( static_cast(ui->arpTypeComboBox->itemData(i).toInt()))) { ui->arpTypeComboBox->setCurrentIndex(i); break; } } if (instSSG->getArpeggioEnabled()) { ui->arpEditGroupBox->setChecked(true); onArpeggioNumberChanged(); } else { ui->arpEditGroupBox->setChecked(false); } } /********** Slots **********/ void InstrumentEditorSSGForm::onArpeggioNumberChanged() { // Change users view std::vector users = bt_.lock()->getArpeggioSSGUsers(ui->arpNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->arpUsersLineEdit->setText(l.join(",")); } void InstrumentEditorSSGForm::onArpeggioParameterChanged(int tnNum) { if (ui->arpNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentArpeggioParameters(); } } void InstrumentEditorSSGForm::onArpeggioTypeChanged(int index) { Q_UNUSED(index) auto type = static_cast( ui->arpTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setArpeggioSSGType(ui->arpNumSpinBox->value(), convertSequenceTypeForData(type)); emit arpeggioParameterChanged(ui->arpNumSpinBox->value(), instNum_); emit modified(); } ui->arpEditor->setSequenceType(type); } void InstrumentEditorSSGForm::on_arpEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGArpeggioEnabled(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } void InstrumentEditorSSGForm::on_arpNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGArpeggio(instNum_, arg1); setInstrumentArpeggioParameters(); emit arpeggioNumberChanged(); emit modified(); } onArpeggioNumberChanged(); } //--- Pitch void InstrumentEditorSSGForm::setInstrumentPitchParameters() { Ui::EventGuard ev(isIgnoreEvent_); std::unique_ptr inst = bt_.lock()->getInstrument(instNum_); auto instSSG = dynamic_cast(inst.get()); ui->ptNumSpinBox->setValue(instSSG->getPitchNumber()); ui->ptEditor->clearData(); for (auto& com : instSSG->getPitchSequence()) { ui->ptEditor->addSequenceCommand(com.type); } for (auto& l : instSSG->getPitchLoops()) { ui->ptEditor->addLoop(l.begin, l.end, l.times); } ui->ptEditor->setRelease(convertReleaseTypeForUI(instSSG->getPitchRelease().type), instSSG->getPitchRelease().begin); for (int i = 0; i < ui->ptTypeComboBox->count(); ++i) { if (instSSG->getPitchType() == convertSequenceTypeForData( static_cast(ui->ptTypeComboBox->itemData(i).toInt()))) { ui->ptTypeComboBox->setCurrentIndex(i); break; } } if (instSSG->getPitchEnabled()) { ui->ptEditGroupBox->setChecked(true); onPitchNumberChanged(); } else { ui->ptEditGroupBox->setChecked(false); } } /********** Slots **********/ void InstrumentEditorSSGForm::onPitchNumberChanged() { // Change users view std::vector users = bt_.lock()->getPitchSSGUsers(ui->ptNumSpinBox->value()); QStringList l; std::transform(users.begin(), users.end(), std::back_inserter(l), [](int n) { return QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); }); ui->ptUsersLineEdit->setText(l.join(",")); } void InstrumentEditorSSGForm::onPitchParameterChanged(int tnNum) { if (ui->ptNumSpinBox->value() == tnNum) { Ui::EventGuard eg(isIgnoreEvent_); setInstrumentPitchParameters(); } } void InstrumentEditorSSGForm::onPitchTypeChanged(int index) { Q_UNUSED(index) auto type = static_cast(ui->ptTypeComboBox->currentData(Qt::UserRole).toInt()); if (!isIgnoreEvent_) { bt_.lock()->setPitchSSGType(ui->ptNumSpinBox->value(), convertSequenceTypeForData(type)); emit pitchParameterChanged(ui->ptNumSpinBox->value(), instNum_); emit modified(); } ui->ptEditor->setSequenceType(type); } void InstrumentEditorSSGForm::on_ptEditGroupBox_toggled(bool arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGPitchEnabled(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } void InstrumentEditorSSGForm::on_ptNumSpinBox_valueChanged(int arg1) { if (!isIgnoreEvent_) { bt_.lock()->setInstrumentSSGPitch(instNum_, arg1); setInstrumentPitchParameters(); emit pitchNumberChanged(); emit modified(); } onPitchNumberChanged(); } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_ssg_form.hpp000066400000000000000000000075761362177441300315240ustar00rootroot00000000000000#ifndef INSTRUMENT_EDITOR_SSG_FORM_HPP #define INSTRUMENT_EDITOR_SSG_FORM_HPP #include #include #include #include "bamboo_tracker.hpp" #include "instrument.hpp" #include "configuration.hpp" #include "jam_manager.hpp" #include "gui/instrument_editor/visualized_instrument_macro_editor.hpp" #include "gui/color_palette.hpp" namespace Ui { class InstrumentEditorSSGForm; } class InstrumentEditorSSGForm : public QWidget { Q_OBJECT public: InstrumentEditorSSGForm(int num, QWidget *parent = nullptr); ~InstrumentEditorSSGForm() override; int getInstrumentNumber() const; void setCore(std::weak_ptr core); void setConfiguration(std::weak_ptr config); void setColorPalette(std::shared_ptr palette); signals: void jamKeyOnEvent(JamKey key); void jamKeyOffEvent(JamKey key); void octaveChanged(bool upFlag); void modified(); /// 0: play song /// 1: play from start /// 2: play pattern /// 3: play from cursor /// -1: stop void playStatusChanged(int stat); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; private: Ui::InstrumentEditorSSGForm *ui; int instNum_; bool isIgnoreEvent_; std::weak_ptr bt_; std::weak_ptr config_; SequenceType convertSequenceTypeForData(VisualizedInstrumentMacroEditor::SequenceType type); VisualizedInstrumentMacroEditor::SequenceType convertSequenceTypeForUI(SequenceType type); ReleaseType convertReleaseTypeForData(VisualizedInstrumentMacroEditor::ReleaseType type); VisualizedInstrumentMacroEditor::ReleaseType convertReleaseTypeForUI(ReleaseType type); void updateInstrumentParameters(); //========== Wave form ==========// signals: void waveFormNumberChanged(); void waveFormParameterChanged(int wfNum, int fromInstNum); public slots: void onWaveFormNumberChanged(); void onWaveFormParameterChanged(int wfNum); private: void setInstrumentWaveFormParameters(); void setWaveFormSequenceColumn(int col); private slots: void on_waveEditGroupBox_toggled(bool arg1); void on_waveNumSpinBox_valueChanged(int arg1); void on_squareMaskRawSpinBox_valueChanged(int arg1); //========== Tone/Noise ==========// signals: void toneNoiseNumberChanged(); void toneNoiseParameterChanged(int tnNum, int fromInstNum); public slots: void onToneNoiseNumberChanged(); void onToneNoiseParameterChanged(int tnNum); private: void setInstrumentToneNoiseParameters(); private slots: void on_tnEditGroupBox_toggled(bool arg1); void on_tnNumSpinBox_valueChanged(int arg1); //========== Envelope ==========// signals: void envelopeNumberChanged(); void envelopeParameterChanged(int wfNum, int fromInstNum); public slots: void onEnvelopeNumberChanged(); void onEnvelopeParameterChanged(int envNum); private: void setInstrumentEnvelopeParameters(); void setEnvelopeSequenceColumn(int col); private slots: void on_envEditGroupBox_toggled(bool arg1); void on_envNumSpinBox_valueChanged(int arg1); void on_hardFreqRawSpinBox_valueChanged(int arg1); //========== Arpeggio ==========// signals: void arpeggioNumberChanged(); void arpeggioParameterChanged(int arpNum, int fromInstNum); public slots: void onArpeggioNumberChanged(); void onArpeggioParameterChanged(int arpNum); private: void setInstrumentArpeggioParameters(); private slots: void onArpeggioTypeChanged(int index); void on_arpEditGroupBox_toggled(bool arg1); void on_arpNumSpinBox_valueChanged(int arg1); //========== Pitch ==========// signals: void pitchNumberChanged(); void pitchParameterChanged(int ptNum, int fromInstNum); public slots: void onPitchNumberChanged(); void onPitchParameterChanged(int arpNum); private: void setInstrumentPitchParameters(); private slots: void onPitchTypeChanged(int index); void on_ptEditGroupBox_toggled(bool arg1); void on_ptNumSpinBox_valueChanged(int arg1); }; #endif // INSTRUMENT_EDITOR_SSG_FORM_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_editor_ssg_form.ui000066400000000000000000000500411362177441300313330ustar00rootroot00000000000000 InstrumentEditorSSGForm 0 0 507 390 Form 0 Waveform Waveform true false false # 127 Qt::NoFocus true Users: Qt::Horizontal 40 20 0 0 Square mask Raw true squareMaskButtonGroup (0x000 | -Hz) 4095 0 Tone/Mask ratio squareMaskButtonGroup 1 8 0 0 / 1 8 Tone/Noise Tone/Noise true false Qt::Horizontal 40 20 Users: false # 127 Qt::NoFocus true Envelope Envelope true false 0 0 Hardware envelope frequency Raw true hardFreqButtonGroup (0x0000 | -Hz) 0 65535 0 Tone/Env ratio hardFreqButtonGroup 1 8 0 0 / 1 8 Qt::Horizontal 40 20 false # 127 Users: Qt::NoFocus true Arpeggio Arpeggio true false Type: false # 127 Qt::Horizontal 40 20 Users: Qt::NoFocus true Pitch Pitch true false Users: Type: Qt::Horizontal 40 20 # 127 Qt::NoFocus true VisualizedInstrumentMacroEditor QWidget
gui/instrument_editor/visualized_instrument_macro_editor.hpp
1
ArpeggioMacroEditor QWidget
gui/instrument_editor/arpeggio_macro_editor.hpp
1
ToneNoiseMacroEditor QWidget
gui/instrument_editor/tone_noise_macro_editor.hpp
1
tabWidget waveEditGroupBox waveNumSpinBox squareMaskRawRadioButton squareMaskRawSpinBox squareMaskRatioRadioButton squareMaskToneSpinBox squareMaskMaskSpinBox tnEditGroupBox tnNumSpinBox envEditGroupBox envNumSpinBox hardFreqRawRadioButton hardFreqRawSpinBox hardFreqRatioRadioButton hardFreqToneSpinBox hardFreqHardSpinBox arpEditGroupBox arpNumSpinBox arpTypeComboBox ptEditGroupBox ptNumSpinBox ptTypeComboBox
BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_form_manager.cpp000066400000000000000000000222341362177441300307530ustar00rootroot00000000000000#include "instrument_form_manager.hpp" #include #include #include #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" #include "misc.hpp" InstrumentFormManager::InstrumentFormManager() { } void InstrumentFormManager::updateByConfiguration() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->updateConfigurationForDisplay(); } } } const std::unique_ptr& InstrumentFormManager::getForm(int n) const { return map_.at(n); } void InstrumentFormManager::add(int n, std::unique_ptr form, QString instName, SoundSource instSrc) { form->setProperty("Name", instName); form->setProperty("Shown", false); form->setProperty("SoundSource", static_cast(instSrc)); map_.emplace(n, std::move(form)); // Parameter numbers update in MainWindow slot } void InstrumentFormManager::remove(int n) { map_.at(n)->close(); switch (getFormInstrumentSoundSource(n)) { case SoundSource::FM: onInstrumentFMEnvelopeNumberChanged(); onInstrumentFMLFONumberChanged(); onInstrumentFMOperatorSequenceNumberChanged(); onInstrumentFMArpeggioNumberChanged(); onInstrumentFMPitchNumberChanged(); break; case SoundSource::SSG: onInstrumentSSGWaveFormNumberChanged(); onInstrumentSSGEnvelopeNumberChanged(); onInstrumentSSGToneNoiseNumberChanged(); onInstrumentSSGArpeggioNumberChanged(); onInstrumentSSGPitchNumberChanged(); break; default: break; } map_.erase(n); } void InstrumentFormManager::showForm(int n) { auto& form = map_.at(n); if (form->isVisible()) { form->activateWindow(); } else { form->setProperty("Shown", true); form->show(); } } void InstrumentFormManager::closeAll() { for (auto& pair : map_) { pair.second->close(); } } void InstrumentFormManager::clearAll() { closeAll(); map_.clear(); } QString InstrumentFormManager::getFormInstrumentName(int n) const { return map_.at(n)->property("Name").toString(); } void InstrumentFormManager::setFormInstrumentName(int n, QString name) { map_.at(n)->setProperty("Name", name); } SoundSource InstrumentFormManager::getFormInstrumentSoundSource(int n) const { return static_cast(map_.at(n)->property("SoundSource").toInt()); } int InstrumentFormManager::checkActivatedFormNumber() const { const QWidget* win = QApplication::activeWindow(); auto it = std::find_if(map_.begin(), map_.end(), [win](const std::pair>& p) { return p.second.get() == win; }); return (it == map_.end() ? -1 : it->first); } /********** Slots **********/ void InstrumentFormManager::onInstrumentFMEnvelopeParameterChanged(int envNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onEnvelopeParameterChanged(envNum); } } } void InstrumentFormManager::onInstrumentFMEnvelopeNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onEnvelopeNumberChanged(); } } } void InstrumentFormManager::onInstrumentFMLFOParameterChanged(int lfoNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onLFOParameterChanged(lfoNum); } } } void InstrumentFormManager::onInstrumentFMLFONumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onLFONumberChanged(); } } } void InstrumentFormManager::onInstrumentFMOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onOperatorSequenceParameterChanged(param, opSeqNum); } } } void InstrumentFormManager::onInstrumentFMOperatorSequenceNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onOperatorSequenceNumberChanged(); } } } void InstrumentFormManager::onInstrumentFMArpeggioParameterChanged(int arpNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onArpeggioParameterChanged(arpNum); } } } void InstrumentFormManager::onInstrumentFMArpeggioNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onArpeggioNumberChanged(); } } } void InstrumentFormManager::onInstrumentFMPitchParameterChanged(int ptNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onPitchParameterChanged(ptNum); } } } void InstrumentFormManager::onInstrumentFMPitchNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::FM) { qobject_cast(pair.second.get())->onPitchNumberChanged(); } } } void InstrumentFormManager::onInstrumentSSGWaveFormParameterChanged(int wfNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onWaveFormParameterChanged(wfNum); } } } void InstrumentFormManager::onInstrumentSSGWaveFormNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onWaveFormNumberChanged(); } } } void InstrumentFormManager::onInstrumentSSGToneNoiseParameterChanged(int tnNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onToneNoiseParameterChanged(tnNum); } } } void InstrumentFormManager::onInstrumentSSGToneNoiseNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onToneNoiseNumberChanged(); } } } void InstrumentFormManager::onInstrumentSSGEnvelopeParameterChanged(int envNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onEnvelopeParameterChanged(envNum); } } } void InstrumentFormManager::onInstrumentSSGEnvelopeNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onEnvelopeNumberChanged(); } } } void InstrumentFormManager::onInstrumentSSGArpeggioParameterChanged(int arpNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onArpeggioParameterChanged(arpNum); } } } void InstrumentFormManager::onInstrumentSSGArpeggioNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onArpeggioNumberChanged(); } } } void InstrumentFormManager::onInstrumentSSGPitchParameterChanged(int ptNum, int fromInstNum) { for (auto& pair : map_) { if (pair.first != fromInstNum && static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onPitchParameterChanged(ptNum); } } } void InstrumentFormManager::onInstrumentSSGPitchNumberChanged() { for (auto& pair : map_) { if (static_cast(pair.second->property("SoundSource").toInt()) == SoundSource::SSG) { qobject_cast(pair.second.get())->onPitchNumberChanged(); } } } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/instrument_form_manager.hpp000066400000000000000000000041211362177441300307530ustar00rootroot00000000000000#ifndef INSTRUMENT_FORM_MANAGER_HPP #define INSTRUMENT_FORM_MANAGER_HPP #include #include #include #include #include #include "instrument/envelope_fm.hpp" #include "misc.hpp" class InstrumentFormManager : public QObject { Q_OBJECT public: InstrumentFormManager(); void updateByConfiguration(); const std::unique_ptr& getForm(int n) const; void remove(int n); void add(int n, std::unique_ptr form, QString instName, SoundSource instSrc); void showForm(int n); void closeAll(); void clearAll(); QString getFormInstrumentName(int n) const; void setFormInstrumentName(int n, QString name); SoundSource getFormInstrumentSoundSource(int n) const; int checkActivatedFormNumber() const; public slots: void onInstrumentFMEnvelopeParameterChanged(int envNum, int fromInstNum); void onInstrumentFMEnvelopeNumberChanged(); void onInstrumentFMLFOParameterChanged(int lfoNum, int fromInstNum); void onInstrumentFMLFONumberChanged(); void onInstrumentFMOperatorSequenceParameterChanged(FMEnvelopeParameter param, int opSeqNum, int fromInstNum); void onInstrumentFMOperatorSequenceNumberChanged(); void onInstrumentFMArpeggioParameterChanged(int arpNum, int fromInstNum); void onInstrumentFMArpeggioNumberChanged(); void onInstrumentFMPitchParameterChanged(int ptNum, int fromInstNum); void onInstrumentFMPitchNumberChanged(); void onInstrumentSSGWaveFormParameterChanged(int wfNum, int fromInstNum); void onInstrumentSSGWaveFormNumberChanged(); void onInstrumentSSGToneNoiseParameterChanged(int tnNum, int fromInstNum); void onInstrumentSSGToneNoiseNumberChanged(); void onInstrumentSSGEnvelopeParameterChanged(int envNum, int fromInstNum); void onInstrumentSSGEnvelopeNumberChanged(); void onInstrumentSSGArpeggioParameterChanged(int arpNum, int fromInstNum); void onInstrumentSSGArpeggioNumberChanged(); void onInstrumentSSGPitchParameterChanged(int ptNum, int fromInstNum); void onInstrumentSSGPitchNumberChanged(); private: std::unordered_map> map_; }; #endif // INSTRUMENT_FORM_MANAGER_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/tone_noise_macro_editor.cpp000066400000000000000000000137761362177441300307320ustar00rootroot00000000000000#include "tone_noise_macro_editor.hpp" #include #include ToneNoiseMacroEditor::ToneNoiseMacroEditor(QWidget *parent) : VisualizedInstrumentMacroEditor(parent) { setMaximumDisplayedRowCount(16); setDefaultRow(0); AddRow(tr("Tone"), false); AddRow(tr("Noise"), false); for (int i = 0; i < 32; ++i) { AddRow(QString::number(i), false); } autoFitLabelWidth(); } ToneNoiseMacroEditor::~ToneNoiseMacroEditor() {} void ToneNoiseMacroEditor::drawField() { QPainter painter(pixmap_.get()); painter.setFont(font_); int dispCnt = getDisplayedRowCount(); int textOffset = fontAscend_ - fontHeight_ + fontLeading_ / 2; // Backgtound // Tone int thi = dispCnt - 1; int ty = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + thi, 0); painter.fillRect(0, ty, panelWidth(), rowHeights_[static_cast(thi)], palette_->tnToneBackColor); // Noise int nhi = dispCnt - 2; int ny = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + nhi, 0); painter.fillRect(0, ny, panelWidth(), rowHeights_[static_cast(nhi)], palette_->tnNoiseBackColor); // Row label painter.setPen(palette_->instSeqTagColor); if (isLabelOmitted_ && !labels_.empty()) { painter.drawText(1, rowHeights_.front() + textOffset, labels_[static_cast(upperRow_)]); int c = dispCnt / 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + c + 1, 0) + textOffset, labels_[static_cast(upperRow_ - c)]); int l = dispCnt - 3; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + l + 1, 0) + textOffset, labels_[static_cast(upperRow_ - l)]); } else { int l = dispCnt - 2; for (int i = 0; i < l; ++i) { painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i + 1, 0) + textOffset, labels_[static_cast(upperRow_ - i)]); } } // Noise painter.setPen(palette_->tnNoiseTextColor); int n = dispCnt - 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + n + 1, 0) + textOffset, labels_[static_cast(1)]); // Tone painter.setPen(palette_->tnToneTextColor); int t = dispCnt - 1; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + t + 1, 0) + textOffset, labels_[static_cast(0)]); for (size_t i = 1; i < cols_.size(); i += 2) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0), 0, colWidths_[i], std::accumulate(rowHeights_.begin(), rowHeights_.begin() + dispCnt - 2, 0), palette_->instSeqOddColColor); } // Sequence painter.setPen(palette_->instSeqCellTextColor); for (size_t i = 0; i < cols_.size(); ++i) { int v = cols_[i].row; int x = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0); if (!v || 32 < v) { // Tone painter.fillRect(x, ty, colWidths_[i], rowHeights_[static_cast(thi)], palette_->tnToneCellColor); painter.drawText(x + 2, ty + rowHeights_[static_cast(thi)] + textOffset, cols_[i].text); } if (0 < v) { // Noise painter.fillRect(x, ny, colWidths_[i], rowHeights_[static_cast(nhi)], palette_->tnNoiseCellColor); painter.drawText(x + 2, ny + rowHeights_[static_cast(nhi)] + textOffset, cols_[i].text); // Noise period int r = (v - 1) % 32 + 2; if (upperRow_ >= r && r > upperRow_ - dispCnt + 2) { int hi = upperRow_ - r; int y = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hi, 0); painter.fillRect(x, y, colWidths_[i], rowHeights_[static_cast(hi)], palette_->instSeqCellColor); painter.drawText(x + 2, y + rowHeights_[static_cast(hi)] + textOffset, cols_[i].text); } } } if (hovCol_ >= 0 && hovRow_ >= 0) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + hovCol_, 0), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hovRow_, 0), colWidths_[static_cast(hovCol_)], rowHeights_[static_cast(hovRow_)], palette_->instSeqHovColor); } } int ToneNoiseMacroEditor::detectRowNumberForMouseEvent(int col, int internalRow) const { int r = upperRow_ - internalRow; int b = upperRow_ - getDisplayedRowCount() + 1; int v = cols_.at(static_cast(col)).row; if (r == b) { // Tone if (!v) return 0; // Tone to Tone else if (v < 33) return v + 32; // Noise to Tone+Noise else return v - 32; // Tone+Noise to Noise } else if (r == b + 1) { // Noise if (!v) return 33; // Tone to Tone+Noise(0) else if (v < 33) return v; // Noise to Noise else return 0; // Tone+Noise to Tone } else { // Noise period r -= 2; // Set noise period if (0 < v && v < 33) return 1 + r; // Noise else return 33 + r; // Tone+Noise } } int ToneNoiseMacroEditor::maxInMML() const { return 65; } QString ToneNoiseMacroEditor::convertSequenceDataUnitToMML(Column col) { if (col.row == 0) return "t"; else { int p = (col.row - 1) % 32; if (col.row < 33) return QString("%1n").arg(p); else return QString("%1tn").arg(p); } } bool ToneNoiseMacroEditor::interpretDataInMML(QString &text, int &cnt, std::vector &column) { // Tone+Noise QRegularExpressionMatch m = QRegularExpression("^(\\d+)(nt|tn)").match(text); if (m.hasMatch()) { int p = m.captured(1).toInt(); if (p < 0 || 31 < p) return false; column.push_back({ 33 + p, -1, "" }); ++cnt; text.remove(QRegularExpression("^\\d+(nt|tn)")); return true; } // Noise m = QRegularExpression("^(\\d+)n").match(text); if (m.hasMatch()) { int p = m.captured(1).toInt(); if (p < 0 || 31 < p) return false; column.push_back({ 1 + p, -1, "" }); ++cnt; text.remove(QRegularExpression("^\\d+n")); return true; } // Noise m = QRegularExpression(R"(^(\d+)?t)").match(text); if (m.hasMatch()) { column.push_back({ 0, -1, "" }); ++cnt; text.remove(QRegularExpression("^(\\d+)?t")); return true; } return false; } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/tone_noise_macro_editor.hpp000066400000000000000000000014151362177441300307220ustar00rootroot00000000000000#ifndef TONENOISEMACROEDITOR_HPP #define TONENOISEMACROEDITOR_HPP #include "visualized_instrument_macro_editor.hpp" class ToneNoiseMacroEditor final : public VisualizedInstrumentMacroEditor { Q_OBJECT // Only change drawing process. // The values of ``cols_`` is left as raw values. // For example, the value of "Tone+Noise 2" is 35. public: explicit ToneNoiseMacroEditor(QWidget *parent = nullptr); ~ToneNoiseMacroEditor() override; protected: void drawField() override; int detectRowNumberForMouseEvent(int col, int internalRow) const override; int maxInMML() const override; QString convertSequenceDataUnitToMML(Column col) override; bool interpretDataInMML(QString &text, int &cnt, std::vector &column) override; }; #endif // TONENOISEMACROEDITOR_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.cpp000066400000000000000000000664361362177441300332400ustar00rootroot00000000000000#include "visualized_instrument_macro_editor.hpp" #include "ui_visualized_instrument_macro_editor.h" #include #include #include #include #include #include #include #include #include #include "gui/event_guard.hpp" VisualizedInstrumentMacroEditor::VisualizedInstrumentMacroEditor(QWidget *parent) : QWidget(parent), font_(QApplication::font()), met_(font_), maxDispRowCnt_(0), upperRow_(-1), defaultRow_(0), hovRow_(-1), hovCol_(-1), type_(NoType), permittedReleaseType_(ReleaseType::FIXED_RELEASE), isLabelOmitted_(false), release_{ ReleaseType::NO_RELEASE, -1 }, ui(new Ui::VisualizedInstrumentMacroEditor), pressRow_(-1), pressCol_(-1), grabLoop_(-1), isGrabLoopHead_(false), isGrabRelease_(false), isMultiReleaseState_(false), mmlBase_(0), isIgnoreEvent_(false) { ui->setupUi(this); /* Font */ font_.setPointSize(10); // Check font size #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) fontWidth_ = met_.horizontalAdvance('0'); #else fontWidth_ = met_.width('0'); #endif fontAscend_ = met_.ascent(); fontHeight_ = met_.height(); fontLeading_ = met_.leading(); /* Width & height */ autoFitLabelWidth(); ui->panel->setAttribute(Qt::WA_Hover); ui->verticalScrollBar->setVisible(false); ui->panel->installEventFilter(this); } VisualizedInstrumentMacroEditor::~VisualizedInstrumentMacroEditor() { delete ui; } void VisualizedInstrumentMacroEditor::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void VisualizedInstrumentMacroEditor::AddRow(QString label, bool fitLabelWidth) { labels_.push_back(label); if (static_cast(labels_.size()) <= maxDispRowCnt_) { upperRow_ = static_cast(labels_.size()) - 1; ui->verticalScrollBar->setVisible(false); ui->verticalScrollBar->setMaximum(0); } else { ui->verticalScrollBar->setVisible(true); int max = static_cast(labels_.size()) - maxDispRowCnt_; ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(max); } if (fitLabelWidth) autoFitLabelWidth(); updateRowHeight(); } void VisualizedInstrumentMacroEditor::setMaximumDisplayedRowCount(int count) { maxDispRowCnt_ = count; if (static_cast(labels_.size()) <= maxDispRowCnt_) { upperRow_ = static_cast(labels_.size()) - 1; ui->verticalScrollBar->setVisible(false); ui->verticalScrollBar->setMaximum(0); } else { ui->verticalScrollBar->setVisible(true); int max = static_cast(labels_.size()) - maxDispRowCnt_; ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(max); } updateRowHeight(); } void VisualizedInstrumentMacroEditor::setDefaultRow(int row) { defaultRow_ = row; } int VisualizedInstrumentMacroEditor::getSequenceLength() const { return static_cast(cols_.size()); } void VisualizedInstrumentMacroEditor::setSequenceCommand(int row, int col, QString str, int data) { size_t idx = static_cast(col); cols_.at(idx).row = row; cols_.at(idx).text = str; cols_.at(idx).data = data; ui->panel->update(); printMML(); emit sequenceCommandChanged(row, col); } void VisualizedInstrumentMacroEditor::setText(int col, QString text) { cols_.at(static_cast(col)).text = text; } void VisualizedInstrumentMacroEditor::setData(int col, int data) { cols_.at(static_cast(col)).data = data; printMML(); } int VisualizedInstrumentMacroEditor::getSequenceAt(int col) const { return cols_.at(static_cast(col)).row; } int VisualizedInstrumentMacroEditor::getSequenceDataAt(int col) const { return cols_.at(static_cast(col)).data; } void VisualizedInstrumentMacroEditor::setMultipleReleaseState(bool enabled) { isMultiReleaseState_ = enabled; } void VisualizedInstrumentMacroEditor::addSequenceCommand(int row, QString str, int data) { cols_.push_back({ row, data, str }); updateColumnWidth(); ui->panel->update(); ui->colSizeLabel->setText(tr("Size: %1").arg(cols_.size())); printMML(); emit sequenceCommandAdded(row, static_cast(cols_.size()) - 1); } void VisualizedInstrumentMacroEditor::removeSequenceCommand() { if (cols_.size() == 1) return; cols_.pop_back(); // Modify loop for (size_t i = 0; i < loops_.size();) { if (loops_[i].begin >= static_cast(cols_.size())) { loops_.erase(loops_.begin() + static_cast(i)); } else { if (loops_[i].end >= static_cast(cols_.size())) loops_[i].end = static_cast(cols_.size()) - 1; ++i; } } // Modify release if (release_.point >= static_cast(cols_.size())) release_.point = -1; updateColumnWidth(); ui->panel->update(); ui->colSizeLabel->setText(tr("Size: %1").arg(cols_.size())); printMML(); emit sequenceCommandRemoved(); } void VisualizedInstrumentMacroEditor::addLoop(int begin, int end, int times) { int inx = 0; for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin > begin) { break; } ++inx; } loops_.insert(loops_.begin() + inx, { begin, end, times }); printMML(); onLoopChanged(); } void VisualizedInstrumentMacroEditor::setSequenceType(SequenceType type) { type_ = type; updateLabels(); printMML(); } void VisualizedInstrumentMacroEditor::setPermittedReleaseTypes(int types) { permittedReleaseType_ = types; } void VisualizedInstrumentMacroEditor::setRelease(ReleaseType type, int point) { release_ = { type, point }; printMML(); } void VisualizedInstrumentMacroEditor::clearData() { cols_.clear(); loops_.clear(); release_ = { ReleaseType::NO_RELEASE, -1 }; updateColumnWidth(); printMML(); } void VisualizedInstrumentMacroEditor::clearRow() { labels_.clear(); autoFitLabelWidth(); } void VisualizedInstrumentMacroEditor::setUpperRow(int row) { upperRow_ = row; int pos = upperRow_ + 1 - getDisplayedRowCount(); ui->panel->update(); ui->verticalScrollBar->setValue(ui->verticalScrollBar->maximum() - pos); } void VisualizedInstrumentMacroEditor::setLabel(int row, QString text) { labels_.at(static_cast(row)) = text; autoFitLabelWidth(); ui->panel->update(); } void VisualizedInstrumentMacroEditor::clearAllLabelText() { std::fill(labels_.begin(), labels_.end(), ""); autoFitLabelWidth(); ui->panel->update(); } void VisualizedInstrumentMacroEditor::setLabelDiaplayMode(bool isOmitted) { isLabelOmitted_ = isOmitted; ui->panel->update(); } void VisualizedInstrumentMacroEditor::autoFitLabelWidth() { #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) int w = met_.horizontalAdvance(VisualizedInstrumentMacroEditor::tr("Release") + " "); for (auto& lab : labels_) w = std::max(w, met_.horizontalAdvance(lab + " ")); #else int w = met_.width(VisualizedInstrumentMacroEditor::tr("Release") + " "); for (auto& lab : labels_) w = std::max(w, met_.width(lab + " ")); #endif labWidth_ = w; } /******************************/ void VisualizedInstrumentMacroEditor::initDisplay() { pixmap_ = std::make_unique(ui->panel->geometry().size()); } void VisualizedInstrumentMacroEditor::drawField() { QPainter painter(pixmap_.get()); painter.setFont(font_); int textOffset = fontAscend_ - fontHeight_ + fontLeading_ / 2; // Row label painter.setPen(palette_->instSeqTagColor); int dispCnt = getDisplayedRowCount(); if (isLabelOmitted_ && !labels_.empty()) { painter.drawText(1, rowHeights_.front() + textOffset, labels_[static_cast(upperRow_)]); int c = dispCnt / 2; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + c + 1, 0) + textOffset, labels_[static_cast(upperRow_ - c)]); int l = dispCnt - 1; painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + l + 1, 0) + textOffset, labels_[static_cast(upperRow_ - l)]); } else { for (int i = 0; i < dispCnt; ++i) { painter.drawText(1, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i + 1, 0) + textOffset, labels_[static_cast(upperRow_ - i)]); } } for (size_t i = 1; i < cols_.size(); i += 2) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0), 0, colWidths_[i], fieldHeight_, palette_->instSeqOddColColor); } // Sequence painter.setPen(palette_->instSeqCellTextColor); for (size_t i = 0; i < cols_.size(); ++i) { if (upperRow_ >= cols_[i].row && cols_[i].row > upperRow_ - dispCnt) { int x = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + static_cast(i), 0); int hi = upperRow_ - cols_[i].row; int y = std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hi, 0); painter.fillRect(x, y, colWidths_[i], rowHeights_[static_cast(hi)], palette_->instSeqCellColor); painter.drawText(x + 2, y + rowHeights_[static_cast(upperRow_ - cols_[i].row)] + textOffset, cols_[i].text); } } if (hovCol_ >= 0 && hovRow_ >= 0) { painter.fillRect(labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + hovCol_, 0), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + hovRow_, 0), colWidths_[static_cast(hovCol_)], rowHeights_[static_cast(hovRow_)], palette_->instSeqHovColor); } } void VisualizedInstrumentMacroEditor::drawLoop() { QPainter painter(pixmap_.get()); painter.setFont(font_); painter.fillRect(0, loopY_, panelWidth(), fontHeight_, palette_->instSeqLoopBackColor); painter.setPen(palette_->instSeqLoopTextColor); painter.drawText(1, loopBaseY_, tr("Loop")); int w = labWidth_; int seqLen = static_cast(cols_.size()); for (int i = 0; i < seqLen; ++i) { for (size_t j = 0; j < loops_.size(); ++j) { if (loops_[j].begin <= i && i <= loops_[j].end) { painter.fillRect(w, loopY_, colWidths_[static_cast(i)], fontHeight_, palette_->instSeqLoopColor); if (loops_[j].begin == i) { painter.fillRect(w, loopY_, 2, fontHeight_, palette_->instSeqLoopEdgeColor); QString times = (loops_[j].times == 1) ? "" : QString::number(loops_[j].times); painter.drawText(w + 2, loopBaseY_, tr("Loop %1").arg(times)); } if (loops_[j].end == i) { painter.fillRect(w + colWidths_[static_cast(i)] - 2, loopY_, 2, fontHeight_, palette_->instSeqLoopEdgeColor); } } } if (hovRow_ == -2 && hovCol_ == i) painter.fillRect(w, loopY_, colWidths_[static_cast(i)], fontHeight_, palette_->instSeqHovColor); w += colWidths_[static_cast(i)]; } } void VisualizedInstrumentMacroEditor::drawRelease() { QPainter painter(pixmap_.get()); painter.setFont(font_); painter.fillRect(0, releaseY_, panelWidth(), fontHeight_, palette_->instSeqReleaseBackColor); painter.setPen(palette_->instSeqReleaseTextColor); painter.drawText(1, releaseBaseY_, tr("Release")); int w = labWidth_; int seqLen = static_cast(cols_.size()); for (int i = 0; i < seqLen; ++i) { if (release_.point == i) { painter.fillRect(w, releaseY_, panelWidth() - w, fontHeight_, palette_->instSeqReleaseColor); painter.fillRect(w, releaseY_, 2, fontHeight_, palette_->instSeqReleaseEdgeColor); QString type; switch (release_.type) { case ReleaseType::NO_RELEASE: type = ""; break; case ReleaseType::FIXED_RELEASE: type = tr("Fixed"); break; case ReleaseType::ABSOLUTE_RELEASE: type = tr("Absolute"); break; case ReleaseType::RELATIVE_RELEASE: type = tr("Relative"); break; } painter.setPen(palette_->instSeqReleaseTextColor); painter.drawText(w + 2, releaseBaseY_, type); } if (hovRow_ == -3 && hovCol_ == i) painter.fillRect(w, releaseY_, colWidths_[static_cast(i)], fontHeight_, palette_->instSeqHovColor); w += colWidths_[static_cast(i)]; } } void VisualizedInstrumentMacroEditor::drawBorder() { QPainter painter(pixmap_.get()); painter.setPen(palette_->instSeqBorderColor); painter.drawLine(labWidth_, 0, labWidth_, ui->panel->geometry().height()); for (int i = 1; i < maxDispRowCnt_; ++i) { painter.drawLine(labWidth_, std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i, 0), panelWidth(), std::accumulate(rowHeights_.begin(), rowHeights_.begin() + i, 0)); } } void VisualizedInstrumentMacroEditor::drawShadow() { QPainter painter(pixmap_.get()); painter.fillRect(0, 0, panelWidth(), ui->panel->geometry().height(), palette_->instSeqMaskColor); } void VisualizedInstrumentMacroEditor::updateLabels() { } void VisualizedInstrumentMacroEditor::printMML() { if (cols_.empty()) return; QString text = ""; std::vector lstack; int seqLen = static_cast(cols_.size()); for (int cnt = 0; cnt < seqLen; ++cnt) { if (release_.point == cnt) { switch (release_.type) { case ReleaseType::FIXED_RELEASE: text += "| "; break; case ReleaseType::ABSOLUTE_RELEASE: text += "/ "; break; case ReleaseType::RELATIVE_RELEASE: text += ": "; break; default: break; } } for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin == cnt) { lstack.push_back(loops_[i]); text += "[ "; } else if (loops_[i].begin > cnt) { break; } } text += (convertSequenceDataUnitToMML(cols_[static_cast(cnt)]) + " "); while (!lstack.empty()) { if (lstack.back().end == cnt) { text += "]"; if (lstack.back().times > 1) { text += QString::number(lstack.back().times); } text += " "; lstack.pop_back(); } else { break; } } } ui->lineEdit->setText(text); } QString VisualizedInstrumentMacroEditor::convertSequenceDataUnitToMML(Column col) { return QString::number(col.row + mmlBase_); } void VisualizedInstrumentMacroEditor::interpretMML() { if (cols_.empty()) return; QString text = ui->lineEdit->text(); std::vector column; std::vector loop; std::vector lstack; Release release = { ReleaseType::NO_RELEASE, -1 }; int cnt = 0; while (!text.isEmpty()) { QRegularExpressionMatch m = QRegularExpression("^\\[").match(text); if (m.hasMatch()) { loop.push_back({ cnt, cnt, 1 }); lstack.push_back(loop.size() - 1); text.remove(QRegularExpression("^\\[")); continue; } m = QRegularExpression("^\\](\\d*)").match(text); if (m.hasMatch()) { if (lstack.empty() || cnt == 0) return; loop[lstack.back()].end = cnt - 1; if (!m.captured(1).isEmpty()) { int t = m.captured(1).toInt(); if (t > 1) loop[lstack.back()].times = t; else return; } lstack.pop_back(); text.remove(QRegularExpression("^\\]\\d*")); continue; } if (permittedReleaseType_ & ReleaseType::FIXED_RELEASE) { m = QRegularExpression("^\\|").match(text); if (m.hasMatch()) { if (release.point > -1) return; release = { ReleaseType::FIXED_RELEASE, cnt }; text.remove(QRegularExpression("^\\|")); continue; } } if (permittedReleaseType_ & ReleaseType::ABSOLUTE_RELEASE) { m = QRegularExpression("^/").match(text); if (m.hasMatch()) { if (release.point > -1) return; release = { ReleaseType::ABSOLUTE_RELEASE, cnt }; text.remove(QRegularExpression("^/")); continue; } } if (permittedReleaseType_ & ReleaseType::RELATIVE_RELEASE) { m = QRegularExpression("^:").match(text); if (m.hasMatch()) { if (release.point > -1) return; release = { ReleaseType::RELATIVE_RELEASE, cnt }; text.remove(QRegularExpression("^:")); continue; } } if (interpretDataInMML(text, cnt, column)) continue; // Data m = QRegularExpression("^ +").match(text); if (m.hasMatch()) { text.remove(QRegularExpression("^ +")); continue; } return; } if (column.empty()) return; if (!lstack.empty()) return; if (release.point > -1 && release.point >= static_cast(column.size())) return; while (cols_.size() > 1) removeSequenceCommand(); setSequenceCommand(column.front().row, 0); for (size_t i = 1; i < column.size(); ++i) { addSequenceCommand(column[i].row); } loops_ = loop; onLoopChanged(); release_ = release; emit releaseChanged(release.type, release.point); ui->panel->update(); } bool VisualizedInstrumentMacroEditor::interpretDataInMML(QString& text, int& cnt, std::vector& column) { QRegularExpressionMatch m = QRegularExpression("^(-?\\d+)").match(text); if (m.hasMatch()) { int d = m.captured(1).toInt() - mmlBase_; if (d < 0 || maxInMML() <= d) return false; column.push_back({ d, -1, "" }); ++cnt; text.remove(QRegularExpression("^-?\\d+")); return true; } return false; } int VisualizedInstrumentMacroEditor::checkLoopRegion(int col) { int ret = -1; for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin <= col) { if (loops_[i].end >= col) { ret = static_cast(i); } } else { break; } } return ret; } void VisualizedInstrumentMacroEditor::moveLoop() { if (hovCol_ < 0) return; size_t idx = static_cast(grabLoop_); if (isGrabLoopHead_) { if (hovCol_ < loops_[idx].begin) { if (grabLoop_ > 0 && loops_[idx - 1].end >= hovCol_) { loops_[idx].begin = loops_[idx - 1].end + 1; } else { loops_[idx].begin = hovCol_; } } else if (hovCol_ > loops_[idx].begin) { if (hovCol_ > loops_[idx].end) { loops_.erase(loops_.begin() + grabLoop_); } else { loops_[idx].begin = hovCol_; } } } else { if (hovCol_ < loops_[idx].end) { if (hovCol_ < loops_[idx].begin) { loops_.erase(loops_.begin() + grabLoop_); } else { loops_[idx].end = hovCol_; } } else if (hovCol_ > loops_[idx].end) { if (grabLoop_ < static_cast(loops_.size()) - 1 && loops_[idx + 1].begin <= hovCol_) { loops_[idx].end = loops_[idx + 1].begin - 1; } else { loops_[idx].end = hovCol_; } } } printMML(); } void VisualizedInstrumentMacroEditor::setMMLDisplay0As(int n) { mmlBase_ = n; } int VisualizedInstrumentMacroEditor::panelWidth() const { return ui->panel->geometry().width(); } /********** Events **********/ bool VisualizedInstrumentMacroEditor::eventFilter(QObject*object, QEvent* event) { if (object == ui->panel) { switch (event->type()) { case QEvent::Paint: paintEventInView(dynamic_cast(event)); return false; case QEvent::Resize: resizeEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonPress: if (isEnabled()) mousePressEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonDblClick: if (isEnabled()) mousePressEventInView(dynamic_cast(event)); return false; case QEvent::MouseButtonRelease: if (isEnabled()) mouseReleaseEventInView(dynamic_cast(event)); return false; case QEvent::MouseMove: if (isEnabled()) mouseMoveEventInView(); return true; case QEvent::HoverMove: mouseHoverdEventInView(dynamic_cast(event)); return false; case QEvent::Leave: leaveEventInView(); return false; case QEvent::Wheel: wheelEventInView(dynamic_cast(event)); return false; default: return false; } } return QWidget::eventFilter(object, event); } void VisualizedInstrumentMacroEditor::paintEventInView(QPaintEvent* event) { if (!palette_) return; pixmap_->fill(Qt::black); drawField(); drawLoop(); drawRelease(); drawBorder(); if (!isEnabled()) drawShadow(); QPainter painter(ui->panel); painter.drawPixmap(event->rect(), *pixmap_.get()); } void VisualizedInstrumentMacroEditor::resizeEventInView(QResizeEvent* event) { Q_UNUSED(event) updateRowHeight(); updateColumnWidth(); releaseY_ = ui->panel->geometry().height() - fontHeight_; releaseBaseY_ = releaseY_ + fontAscend_ + fontLeading_ / 2; loopY_ = releaseY_ - fontHeight_; loopBaseY_ = releaseBaseY_ - fontHeight_; fieldHeight_ = loopY_; initDisplay(); } int VisualizedInstrumentMacroEditor::detectRowNumberForMouseEvent(int col, int internalRow) const { Q_UNUSED(col) return upperRow_ - internalRow; } void VisualizedInstrumentMacroEditor::mousePressEventInView(QMouseEvent* event) { if (!cols_.size()) return; pressRow_ = hovRow_; pressCol_ = hovCol_; // Check grab int x = event->pos().x(); if (hovRow_ == -2) { if (event->button() == Qt::LeftButton) { int seqLen = static_cast(cols_.size()); for (int col = 0, w = labWidth_; col < seqLen; ++col) { if (w - 4 < x && x < w + 4) { for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].begin == col) { grabLoop_ = static_cast(i); isGrabLoopHead_ = true; } else if (loops_[i].begin > col) { break; } } } else if (w + colWidths_[static_cast(col)] - 4 < x && x < w + colWidths_[static_cast(col)] + 4) { for (size_t i = 0; i < loops_.size(); ++i) { if (loops_[i].end == col) { grabLoop_ = static_cast(i); isGrabLoopHead_ = false; } else if (loops_[i].end > col) { break; } } } w += colWidths_[static_cast(col)]; } } } else if (hovRow_ == -3 && release_.point != -1) { if (event->button() == Qt::LeftButton) { int w = labWidth_ + std::accumulate(colWidths_.begin(), colWidths_.begin() + release_.point, 0); if (w - 4 < x && x < w + 4) { isGrabRelease_ = true; } } } // Press process if (pressCol_ > -1) { if (pressRow_ == -2) { if (grabLoop_ == -1) { int i = checkLoopRegion(pressCol_); switch (event->button()) { case Qt::LeftButton: { if (i == -1) { // New loop addLoop(pressCol_, pressCol_, 1); } else { // Loop count up ++loops_[static_cast(i)].times; printMML(); onLoopChanged(); } break; } case Qt::RightButton: { if (i > -1) { // Loop count down if (loops_[static_cast(i)].times > 1) { --loops_[static_cast(i)].times; } else { // Erase loop loops_.erase(loops_.begin() + i); } printMML(); onLoopChanged(); } break; } default: break; } } } else if (pressRow_ == -3) { if (!isGrabRelease_) { switch (event->button()) { case Qt::LeftButton: { if (release_.point == -1 || pressCol_ < release_.point) { // New release release_.type = (release_.type == ReleaseType::NO_RELEASE) ? ReleaseType::FIXED_RELEASE : release_.type; release_.point = pressCol_; printMML(); emit releaseChanged(release_.type, release_.point); } else if (isMultiReleaseState_) { // Change release type switch (release_.type) { case ReleaseType::FIXED_RELEASE: release_.type = ReleaseType::ABSOLUTE_RELEASE; break; case ReleaseType::ABSOLUTE_RELEASE: release_.type = ReleaseType::RELATIVE_RELEASE; break; case ReleaseType::NO_RELEASE: case ReleaseType::RELATIVE_RELEASE: release_.type = ReleaseType::FIXED_RELEASE; break; } printMML(); emit releaseChanged(release_.type, release_.point); } break; } case Qt::RightButton: { if (pressCol_ >= release_.point) { // Erase release release_ = { ReleaseType::NO_RELEASE, -1 }; printMML(); emit releaseChanged(release_.type, release_.point); } break; } default: break; } } } else { setSequenceCommand(detectRowNumberForMouseEvent(hovCol_, hovRow_), hovCol_); prevPressRow_ = hovRow_; prevPressCol_ = hovCol_; } } ui->panel->update(); } void VisualizedInstrumentMacroEditor::mouseReleaseEventInView(QMouseEvent* event) { if (!cols_.size()) return; if (grabLoop_ != -1) { // Move loop if (event->button() == Qt::LeftButton) { moveLoop(); onLoopChanged(); } } else if (isGrabRelease_) { // Move release if (event->button() == Qt::LeftButton) { if (hovCol_ > -1) { release_.point = hovCol_; printMML(); emit releaseChanged(release_.type, release_.point); } } } pressRow_ = -1; pressCol_ = -1; prevPressRow_ = -1; prevPressCol_ = -1; grabLoop_ = -1; isGrabLoopHead_ = false; isGrabRelease_ = false; ui->panel->update(); } void VisualizedInstrumentMacroEditor::mouseMoveEventInView() { if (!cols_.size()) return; if (pressRow_ >= 0 && pressCol_ >= 0 && hovRow_ >= 0 && hovCol_ >= 0) { if (prevPressRow_ != hovRow_ || prevPressCol_ != hovCol_) { prevPressRow_ = hovRow_; prevPressCol_ = hovCol_; setSequenceCommand(detectRowNumberForMouseEvent(hovCol_, hovRow_), hovCol_); } } } void VisualizedInstrumentMacroEditor::mouseHoverdEventInView(QHoverEvent* event) { if (!cols_.size()) return; int oldCol = hovCol_; int oldRow = hovRow_; QPoint pos = event->pos(); // Detect column if (pos.x() < labWidth_) { hovCol_ = -2; } else { int seqLen = static_cast(cols_.size()); for (int i = 0, w = labWidth_; i < seqLen; ++i) { w += colWidths_[static_cast(i)]; if (pos.x() < w) { hovCol_ = i; break; } } if (hovCol_ >= seqLen) hovCol_ = -1; // Out of range } // Detect row if (releaseY_ < pos.y()) { hovRow_ = -3; } else if (loopY_ < pos.y()) { hovRow_ = -2; } else { int cnt = getDisplayedRowCount(); for (int i = 0, w = 0; i < cnt; ++i) { w += rowHeights_[static_cast(i)]; if (pos.y() < w) { hovRow_ = i; break; } } } if (hovRow_ != oldRow || hovCol_ != oldCol) ui->panel->update(); } void VisualizedInstrumentMacroEditor::leaveEventInView() { hovRow_ = -1; hovCol_ = -1; ui->panel->update(); } void VisualizedInstrumentMacroEditor::wheelEventInView(QWheelEvent* event) { if (!cols_.size()) return; Ui::EventGuard eg(isIgnoreEvent_); int degree = - event->angleDelta().y() / 8; int pos = ui->verticalScrollBar->value() + degree / 15; int labCnt = static_cast(labels_.size()); if (0 > pos) pos = 0; else if (pos > labCnt - maxDispRowCnt_) pos = labCnt - maxDispRowCnt_; scrollUp(ui->verticalScrollBar->maximum() - pos); ui->panel->update(); ui->verticalScrollBar->setValue(pos); } void VisualizedInstrumentMacroEditor::on_colIncrToolButton_clicked() { addSequenceCommand(defaultRow_); } void VisualizedInstrumentMacroEditor::on_colDecrToolButton_clicked() { removeSequenceCommand(); } void VisualizedInstrumentMacroEditor::on_verticalScrollBar_valueChanged(int value) { if (!isIgnoreEvent_) { scrollUp(ui->verticalScrollBar->maximum() - value); ui->panel->update(); } } int VisualizedInstrumentMacroEditor::maxInMML() const { return static_cast(labels_.size()); } void VisualizedInstrumentMacroEditor::on_lineEdit_editingFinished() { interpretMML(); printMML(); } void VisualizedInstrumentMacroEditor::onLoopChanged() { std::vector begins, ends, times; for (auto& l : loops_) { begins.push_back(l.begin); ends.push_back(l.end); times.push_back(l.times); } emit loopChanged(std::move(begins), std::move(ends), std::move(times)); } void VisualizedInstrumentMacroEditor::updateColumnWidth() { colWidths_.clear(); if (!cols_.size()) return; float ww = (panelWidth() - labWidth_) / static_cast(cols_.size()); int w = static_cast(ww); float dif = ww - w; float sum = 0; for (size_t i = 0; i < cols_.size(); ++i) { int width = w; sum += dif; if (sum >= 1.0f) { ++width; sum -= 1.0f; } colWidths_.push_back(width); } } void VisualizedInstrumentMacroEditor::updateRowHeight() { rowHeights_.clear(); if (!labels_.size()) return; int div = getDisplayedRowCount(); float hh = (ui->panel->geometry().height() - fontHeight_ * 2) / static_cast(div); int h = static_cast(hh); float dif = hh - h; float sum = 0; for (int i = 0; i < div; ++i) { int height = h; sum += dif; if (sum >= 1.0f) { ++height; sum -= 1.0f; } rowHeights_.push_back(height); } } BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.hpp000066400000000000000000000112141362177441300332250ustar00rootroot00000000000000#ifndef VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP #define VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gui/color_palette.hpp" namespace Ui { class VisualizedInstrumentMacroEditor; } class VisualizedInstrumentMacroEditor : public QWidget { Q_OBJECT public: explicit VisualizedInstrumentMacroEditor(QWidget *parent = nullptr); ~VisualizedInstrumentMacroEditor() override; void setColorPalette(std::shared_ptr palette); void AddRow(QString label = "", bool fitLabelWidth = true); void setMaximumDisplayedRowCount(int count); void setDefaultRow(int row); int getSequenceLength() const; void setSequenceCommand(int row, int col, QString str = "", int data = -1); void setText(int col, QString text); void setData(int col, int data); int getSequenceAt(int col) const; int getSequenceDataAt(int col) const; void setMultipleReleaseState(bool enabled); void addSequenceCommand(int row, QString str = "", int data = -1); void removeSequenceCommand(); void addLoop(int begin, int end, int times); enum SequenceType { NoType, FixedSequence, AbsoluteSequence, RelativeSequence }; void setSequenceType(SequenceType type); enum ReleaseType : int { NO_RELEASE = 0, FIXED_RELEASE = 1, ABSOLUTE_RELEASE = 2, RELATIVE_RELEASE = 4 }; void setPermittedReleaseTypes(int types); void setRelease(ReleaseType type, int point); void clearData(); void clearRow(); void setUpperRow(int row); void setLabel(int row, QString text); void clearAllLabelText(); void setLabelDiaplayMode(bool isOmitted); void autoFitLabelWidth(); void setMMLDisplay0As(int n); signals: void sequenceCommandChanged(int row, int col); void sequenceCommandAdded(int row, int col); void sequenceCommandRemoved(); void loopChanged(std::vector begins, std::vector ends, std::vector times); void releaseChanged(ReleaseType type, int point); protected: bool eventFilter(QObject*object, QEvent* event) override; void paintEventInView(QPaintEvent* event); void resizeEventInView(QResizeEvent* event); void mousePressEventInView(QMouseEvent* event); void mouseReleaseEventInView(QMouseEvent* event); void mouseMoveEventInView(); void mouseHoverdEventInView(QHoverEvent* event); void leaveEventInView(); void wheelEventInView(QWheelEvent* event); std::unique_ptr pixmap_; std::shared_ptr palette_; QFont font_; QFontMetrics met_; int fontWidth_, fontHeight_, fontAscend_, fontLeading_; int labWidth_; std::vector rowHeights_, colWidths_; int rowHeight_; int fieldHeight_; int maxDispRowCnt_; int upperRow_, defaultRow_; int hovRow_, hovCol_; virtual void drawField(); virtual void updateLabels(); SequenceType type_; struct Column { int row, data; QString text; }; std::vector labels_; std::vector cols_; int permittedReleaseType_; bool isLabelOmitted_; virtual int detectRowNumberForMouseEvent(int col, int internalRow) const; struct Loop { int begin, end, times; }; struct Release { ReleaseType type; int point; }; std::vector loops_; Release release_; void printMML(); virtual QString convertSequenceDataUnitToMML(Column col); virtual int maxInMML() const; void interpretMML(); virtual bool interpretDataInMML(QString& text, int& cnt, std::vector& column); int panelWidth() const; inline int getDisplayedRowCount() const { int labCnt = static_cast(labels_.size()); return (maxDispRowCnt_ > labCnt) ? labCnt : maxDispRowCnt_; } private slots: void on_colIncrToolButton_clicked(); void on_colDecrToolButton_clicked(); void on_verticalScrollBar_valueChanged(int value); void on_lineEdit_editingFinished(); void onLoopChanged(); private: Ui::VisualizedInstrumentMacroEditor *ui; int pressRow_, pressCol_; int prevPressRow_, prevPressCol_; int loopY_, releaseY_; int loopBaseY_, releaseBaseY_; int grabLoop_; bool isGrabLoopHead_; bool isGrabRelease_; bool isMultiReleaseState_; int mmlBase_; bool isIgnoreEvent_; void initDisplay(); void drawLoop(); void drawRelease(); void drawBorder(); void drawShadow(); int checkLoopRegion(int col); void moveLoop(); void updateColumnWidth(); void updateRowHeight(); inline void scrollUp(int pos) { upperRow_ = pos - 1 + getDisplayedRowCount(); } }; #endif // VISUALIZED_INSTRUMENT_MACRO_EDITOR_HPP BambooTracker-0.3.5/BambooTracker/gui/instrument_editor/visualized_instrument_macro_editor.ui000066400000000000000000000040431362177441300330550ustar00rootroot00000000000000 VisualizedInstrumentMacroEditor 0 0 400 300 Form 0 0 0 0 0 Qt::Vertical false true - + Size: 1 BambooTracker-0.3.5/BambooTracker/gui/instrument_selection_dialog.cpp000066400000000000000000000034701362177441300260450ustar00rootroot00000000000000#include "instrument_selection_dialog.hpp" #include "ui_instrument_selection_dialog.h" #include "bank.hpp" InstrumentSelectionDialog::InstrumentSelectionDialog(const AbstractBank &bank, const QString &text, QWidget *parent) : QDialog(parent), bank_(bank), ui_(new Ui::InstrumentSelectionDialog) { ui_->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui_->label->setText(text); setupContents(); } InstrumentSelectionDialog::~InstrumentSelectionDialog() { } void InstrumentSelectionDialog::setupContents() { QListWidget *lw = ui_->listWidget; lw->setSelectionMode(QListWidget::MultiSelection); size_t instCount = bank_.getNumInstruments(); for (size_t i = 0; i < instCount; ++i) { QString id = QString::fromStdString(bank_.getInstrumentIdentifier(i)); QString name = QString::fromStdString(bank_.getInstrumentName(i)); QListWidgetItem *item = new QListWidgetItem(QString("%1 %2").arg(id).arg(name)); item->setData(Qt::UserRole, static_cast(i)); lw->addItem(item); } } QVector InstrumentSelectionDialog::currentInstrumentSelection() const { QListWidget *lw = ui_->listWidget; QList items = lw->selectedItems(); QVector selection; selection.reserve(items.size()); for (QListWidgetItem *item : items) { size_t index = static_cast(item->data(Qt::UserRole).toULongLong()); selection.push_back(index); } return selection; } void InstrumentSelectionDialog::on_searchLineEdit_textChanged(const QString &search) { QListWidget *lw = ui_->listWidget; unsigned count = static_cast(lw->count()); for (unsigned row = 0; row < count; ++row) { QListWidgetItem *item = lw->item(static_cast(row)); bool accept = search.isEmpty() || item->text().contains(search, Qt::CaseInsensitive); item->setHidden(!accept); } } BambooTracker-0.3.5/BambooTracker/gui/instrument_selection_dialog.hpp000066400000000000000000000011241362177441300260440ustar00rootroot00000000000000#pragma once #include #include #include class AbstractBank; namespace Ui { class InstrumentSelectionDialog; }; class InstrumentSelectionDialog : public QDialog { Q_OBJECT public: InstrumentSelectionDialog(const AbstractBank &bank, const QString &text, QWidget *parent = nullptr); ~InstrumentSelectionDialog(); QVector currentInstrumentSelection() const; private: const AbstractBank &bank_; std::unique_ptr ui_; void setupContents(); private slots: void on_searchLineEdit_textChanged(const QString &search); }; BambooTracker-0.3.5/BambooTracker/gui/instrument_selection_dialog.ui000066400000000000000000000035251362177441300257010ustar00rootroot00000000000000 InstrumentSelectionDialog 0 0 400 300 Select instruments Search... Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() InstrumentSelectionDialog accept() 248 254 157 274 buttonBox rejected() InstrumentSelectionDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/jam_layout.hpp000066400000000000000000000022731362177441300224220ustar00rootroot00000000000000#ifndef JAM_LAYOUT_HPP #define JAM_LAYOUT_HPP #include #include #include #include "configuration.hpp" #include "jam_manager.hpp" // Layout decipherer inline JamKey getJamKeyFromLayoutMapping(Qt::Key key, std::weak_ptr config) { std::shared_ptr configLocked = config.lock(); Configuration::KeyboardLayout selectedLayout = configLocked->getNoteEntryLayout(); if (configLocked->mappingLayouts.find (selectedLayout) != configLocked->mappingLayouts.end()) { std::unordered_map selectedLayoutMapping = configLocked->mappingLayouts.at (selectedLayout); auto it = std::find_if(selectedLayoutMapping.begin(), selectedLayoutMapping.end(), [key](const std::pair& t) -> bool { return (QKeySequence(key).matches(QKeySequence(QString::fromStdString(t.first))) == QKeySequence::ExactMatch); }); if (it != selectedLayoutMapping.end()) { return (*it).second; } else { throw std::invalid_argument("Unmapped key"); } //something has gone wrong, current layout has no layout map //TODO: handle cleanly? } else throw std::out_of_range("Unmapped Layout"); } #endif // JAM_LAYOUT_HPP BambooTracker-0.3.5/BambooTracker/gui/keyboard_shortcut_list_dialog.cpp000066400000000000000000000006051362177441300263530ustar00rootroot00000000000000#include "keyboard_shortcut_list_dialog.hpp" #include "ui_keyboard_shortcut_list_dialog.h" KeyboardShortcutListDialog::KeyboardShortcutListDialog(QWidget *parent) : QDialog(parent), ui(new Ui::KeyboardShortcutListDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); } KeyboardShortcutListDialog::~KeyboardShortcutListDialog() { delete ui; } BambooTracker-0.3.5/BambooTracker/gui/keyboard_shortcut_list_dialog.hpp000066400000000000000000000006451362177441300263640ustar00rootroot00000000000000#ifndef KEYBOARD_SHORTCUT_LIST_DIALOG_HPP #define KEYBOARD_SHORTCUT_LIST_DIALOG_HPP #include namespace Ui { class KeyboardShortcutListDialog; } class KeyboardShortcutListDialog : public QDialog { Q_OBJECT public: explicit KeyboardShortcutListDialog(QWidget *parent = nullptr); ~KeyboardShortcutListDialog(); private: Ui::KeyboardShortcutListDialog *ui; }; #endif // KEYBOARD_SHORTCUT_LIST_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/keyboard_shortcut_list_dialog.ui000066400000000000000000000032131362177441300262040ustar00rootroot00000000000000 KeyboardShortcutListDialog 0 0 400 300 Keyboard shortcuts qrc:/doc/shortcuts Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() KeyboardShortcutListDialog accept() 248 254 157 274 buttonBox rejected() KeyboardShortcutListDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/labeled_horizontal_slider.cpp000066400000000000000000000061001362177441300254450ustar00rootroot00000000000000#include "labeled_horizontal_slider.hpp" #include "ui_labeled_horizontal_slider.h" #include #include "slider_style.hpp" LabeledHorizontalSlider::LabeledHorizontalSlider(QWidget *parent) : QFrame(parent), ui(new Ui::LabeledHorizontalSlider) { init("", "", ""); } LabeledHorizontalSlider::LabeledHorizontalSlider(QString text, QString prefix, QString suffix, QWidget *parent) : QFrame(parent), ui(new Ui::LabeledHorizontalSlider) { init(text, prefix, suffix); } void LabeledHorizontalSlider::init(QString text, QString prefix, QString suffix) { ui->setupUi(this); rate_ = 1.0; precision_ = 0; isSigned_ = false; ui->textLabel->setText(text); prefix_ = prefix; suffix_ = suffix; updateValueLabel(); ui->slider->setStyle(new SliderStyle()); ui->slider->installEventFilter(this); } LabeledHorizontalSlider::~LabeledHorizontalSlider() { delete ui; } int LabeledHorizontalSlider::value() const { return ui->slider->value(); } void LabeledHorizontalSlider::setValue(int value) { ui->slider->setValue(value); } int LabeledHorizontalSlider::maximum() const { return ui->slider->maximum(); } void LabeledHorizontalSlider::setMaximum(int value) { ui->slider->setMaximum(value); } int LabeledHorizontalSlider::minimum() const { return ui->slider->minimum(); } void LabeledHorizontalSlider::setMinimum(int value) { ui->slider->setMinimum(value); } void LabeledHorizontalSlider::setValueRate(double rate, int precision) { rate_ = rate; precision_ = precision; updateValueLabel(); } void LabeledHorizontalSlider::setSign(bool enabled) { isSigned_ = enabled; updateValueLabel(); } void LabeledHorizontalSlider::setTickPosition(QSlider::TickPosition position) { ui->slider->setTickPosition(position); } void LabeledHorizontalSlider::setTickInterval(int ti) { ui->slider->setTickInterval(ti); } QString LabeledHorizontalSlider::text() const { return ui->textLabel->text(); } void LabeledHorizontalSlider::setText(QString text) { ui->textLabel->setText(text); } QString LabeledHorizontalSlider::suffix() const { return suffix_; } void LabeledHorizontalSlider::setSuffix(QString suffix) { suffix_ = suffix; updateValueLabel(); } QString LabeledHorizontalSlider::prefix() const { return prefix_; } void LabeledHorizontalSlider::setprefix(QString prefix) { prefix_ = prefix; updateValueLabel(); } bool LabeledHorizontalSlider::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->slider) { if (event->type() == QEvent::Wheel) { auto e = dynamic_cast(event); if (e->angleDelta().y() > 0) ui->slider->setValue(ui->slider->value() + 1); else if (e->angleDelta().y() < 0) ui->slider->setValue(ui->slider->value() - 1); return true; } } return QFrame::eventFilter(watched, event); } void LabeledHorizontalSlider::on_slider_valueChanged(int value) { updateValueLabel(); emit valueChanged(value); } void LabeledHorizontalSlider::updateValueLabel() { QString sign = (isSigned_ && ui->slider->value() > -1) ? "+" : ""; ui->valueLabel->setText( prefix_ + sign + QString::number(ui->slider->value() * rate_, 'f', precision_) + suffix_); } BambooTracker-0.3.5/BambooTracker/gui/labeled_horizontal_slider.hpp000066400000000000000000000025531362177441300254620ustar00rootroot00000000000000#ifndef LABALED_HORIZONTAL_SLIDER_HPP #define LABALED_HORIZONTAL_SLIDER_HPP #include #include #include #include namespace Ui { class LabeledHorizontalSlider; } class LabeledHorizontalSlider : public QFrame { Q_OBJECT public: explicit LabeledHorizontalSlider(QWidget *parent = nullptr); LabeledHorizontalSlider(QString text, QString prefix = "", QString suffix = "", QWidget *parent = nullptr); ~LabeledHorizontalSlider() override; int value() const; void setValue(int value); int maximum() const; void setMaximum(int value); int minimum() const; void setMinimum(int value); void setValueRate(double rate, int precision = 1); void setSign(bool enabled); void setTickPosition(QSlider::TickPosition position); void setTickInterval(int ti); QString text() const; void setText(QString text); QString suffix() const; void setSuffix(QString suffix); QString prefix() const; void setprefix(QString prefix); signals: void valueChanged(int value); protected: bool eventFilter(QObject* watched, QEvent* event) override; private slots: void on_slider_valueChanged(int value); private: Ui::LabeledHorizontalSlider *ui; QString suffix_, prefix_; double rate_; int precision_; bool isSigned_; void init(QString text, QString prefix, QString suffix); void updateValueLabel(); }; #endif // LABALED_HORIZONTAL_SLIDER_HPP BambooTracker-0.3.5/BambooTracker/gui/labeled_horizontal_slider.ui000066400000000000000000000027411362177441300253070ustar00rootroot00000000000000 LabeledHorizontalSlider 0 0 136 28 Frame LabeledHorizontalSlider { border: 0; } 3 0 0 0 0 Text Qt::Horizontal 0 BambooTracker-0.3.5/BambooTracker/gui/labeled_vertical_slider.cpp000066400000000000000000000060041362177441300250700ustar00rootroot00000000000000#include "labeled_vertical_slider.hpp" #include "ui_labeled_vertical_slider.h" #include #include "slider_style.hpp" LabeledVerticalSlider::LabeledVerticalSlider(QWidget *parent) : QFrame(parent), ui(new Ui::LabeledVerticalSlider) { init("", "", ""); } LabeledVerticalSlider::LabeledVerticalSlider(QString text, QString prefix, QString suffix, QWidget *parent) : QFrame(parent), ui(new Ui::LabeledVerticalSlider) { init(text, prefix, suffix); } void LabeledVerticalSlider::init(QString text, QString prefix, QString suffix) { ui->setupUi(this); rate_ = 1.0; precision_ = 0; isSigned_ = false; ui->textLabel->setText(text); prefix_ = prefix; suffix_ = suffix; updateValueLabel(); ui->slider->setStyle(new SliderStyle()); ui->slider->installEventFilter(this); } LabeledVerticalSlider::~LabeledVerticalSlider() { delete ui; } int LabeledVerticalSlider::value() const { return ui->slider->value(); } void LabeledVerticalSlider::setValue(int value) { ui->slider->setValue(value); } int LabeledVerticalSlider::maximum() const { return ui->slider->maximum(); } void LabeledVerticalSlider::setMaximum(int value) { ui->slider->setMaximum(value); } int LabeledVerticalSlider::minimum() const { return ui->slider->minimum(); } void LabeledVerticalSlider::setMinimum(int value) { ui->slider->setMinimum(value); } void LabeledVerticalSlider::setValueRate(double rate, int precision) { rate_ = rate; precision_ = precision; updateValueLabel(); } void LabeledVerticalSlider::setSign(bool enabled) { isSigned_ = enabled; updateValueLabel(); } void LabeledVerticalSlider::setTickPosition(QSlider::TickPosition position) { ui->slider->setTickPosition(position); } void LabeledVerticalSlider::setTickInterval(int ti) { ui->slider->setTickInterval(ti); } QString LabeledVerticalSlider::text() const { return ui->textLabel->text(); } void LabeledVerticalSlider::setText(QString text) { ui->textLabel->setText(text); } QString LabeledVerticalSlider::suffix() const { return suffix_; } void LabeledVerticalSlider::setSuffix(QString suffix) { suffix_ = suffix; updateValueLabel(); } QString LabeledVerticalSlider::prefix() const { return prefix_; } void LabeledVerticalSlider::setprefix(QString prefix) { prefix_ = prefix; updateValueLabel(); } bool LabeledVerticalSlider::eventFilter(QObject* watched, QEvent* event) { if (watched == ui->slider) { if (event->type() == QEvent::Wheel) { auto e = dynamic_cast(event); if (e->angleDelta().y() > 0) ui->slider->setValue(ui->slider->value() + 1); else if (e->angleDelta().y() < 0) ui->slider->setValue(ui->slider->value() - 1); return true; } } return QFrame::eventFilter(watched, event); } void LabeledVerticalSlider::on_slider_valueChanged(int value) { updateValueLabel(); emit valueChanged(value); } void LabeledVerticalSlider::updateValueLabel() { QString sign = (isSigned_ && ui->slider->value() > -1) ? "+" : ""; ui->valueLabel->setText( prefix_ + sign + QString::number(ui->slider->value() * rate_, 'f', precision_) + suffix_); } BambooTracker-0.3.5/BambooTracker/gui/labeled_vertical_slider.hpp000066400000000000000000000025311362177441300250760ustar00rootroot00000000000000#ifndef LABELED_VERTICAL_SLIDER_HPP #define LABELED_VERTICAL_SLIDER_HPP #include #include #include #include namespace Ui { class LabeledVerticalSlider; } class LabeledVerticalSlider : public QFrame { Q_OBJECT public: explicit LabeledVerticalSlider(QWidget *parent = nullptr); LabeledVerticalSlider(QString text, QString prefix = "", QString suffix = "", QWidget *parent = nullptr); ~LabeledVerticalSlider() override; int value() const; void setValue(int value); int maximum() const; void setMaximum(int value); int minimum() const; void setMinimum(int value); void setValueRate(double rate, int precision = 1); void setSign(bool enabled); void setTickPosition(QSlider::TickPosition position); void setTickInterval(int ti); QString text() const; void setText(QString text); QString suffix() const; void setSuffix(QString suffix); QString prefix() const; void setprefix(QString prefix); signals: void valueChanged(int value); protected: bool eventFilter(QObject* watched, QEvent* event) override; private slots: void on_slider_valueChanged(int value); private: Ui::LabeledVerticalSlider *ui; QString suffix_, prefix_; double rate_; int precision_; bool isSigned_; void init(QString text, QString prefix, QString suffix); void updateValueLabel(); }; #endif // LABELED_VERTICAL_SLIDER_HPP BambooTracker-0.3.5/BambooTracker/gui/labeled_vertical_slider.ui000066400000000000000000000031401362177441300247210ustar00rootroot00000000000000 LabeledVerticalSlider 0 0 36 115 Frame LabeledVerticalSlider { border: 0; } 3 0 0 0 0 Text Qt::AlignCenter Qt::Vertical 0 Qt::AlignCenter BambooTracker-0.3.5/BambooTracker/gui/mainwindow.cpp000066400000000000000000002533051362177441300224310ustar00rootroot00000000000000#include "mainwindow.hpp" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jam_manager.hpp" #include "song.hpp" #include "track.hpp" #include "instrument.hpp" #include "bank.hpp" #include "bank_io.hpp" #include "file_io.hpp" #include "version.hpp" #include "gui/command/commands_qt.hpp" #include "gui/instrument_editor/instrument_editor_fm_form.hpp" #include "gui/instrument_editor/instrument_editor_ssg_form.hpp" #include "gui/module_properties_dialog.hpp" #include "gui/groove_settings_dialog.hpp" #include "gui/configuration_dialog.hpp" #include "gui/comment_edit_dialog.hpp" #include "gui/wave_export_settings_dialog.hpp" #include "gui/vgm_export_settings_dialog.hpp" #include "gui/instrument_selection_dialog.hpp" #include "gui/s98_export_settings_dialog.hpp" #include "gui/configuration_handler.hpp" #include "gui/jam_layout.hpp" #include "chips/scci/SCCIDefines.h" #include "chips/c86ctl/c86ctl_wrapper.hpp" #include "gui/file_history_handler.hpp" #include "midi/midi.hpp" #include "audio_stream_rtaudio.hpp" #include "color_palette_handler.hpp" #include "binary_container.hpp" #include "enum_hash.hpp" MainWindow::MainWindow(std::weak_ptr config, QString filePath, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), config_(config), palette_(std::make_shared()), bt_(std::make_shared(config)), comStack_(std::make_shared(this)), fileHistory_(std::make_shared()), scciDll_(std::make_unique("scci")), c86ctlDll_(std::make_unique("c86ctl")), instForms_(std::make_shared()), isModifiedForNotCommand_(false), hasLockedWigets_(false), isEditedPattern_(true), isEditedOrder_(false), isEditedInstList_(false), isSelectedPO_(false), hasShownOnce_(false), firstViewUpdateRequest_(false), effListDiag_(std::make_unique()), shortcutsDiag_(std::make_unique()) { ui->setupUi(this); if (config.lock()->getMainWindowX() == -1) { // When unset QRect rec = geometry(); rec.moveCenter(QGuiApplication::screens().front()->geometry().center()); setGeometry(rec); config.lock()->setMainWindowX(x()); config.lock()->setMainWindowY(y()); } else { move(config.lock()->getMainWindowX(), config.lock()->getMainWindowY()); } resize(config.lock()->getMainWindowWidth(), config.lock()->getMainWindowHeight()); if (config.lock()->getMainWindowMaximized()) showMaximized(); ui->actionFollow_Mode->setChecked(config.lock()->getFollowMode()); ui->waveVisual->setVisible(config_.lock()->getShowWaveVisual()); bt_->setFollowPlay(config.lock()->getFollowMode()); if (config.lock()->getPatternEditorHeaderFont().empty()) { config.lock()->setPatternEditorHeaderFont(ui->patternEditor->getHeaderFont().toStdString()); } if (config.lock()->getPatternEditorRowsFont().empty()) { config.lock()->setPatternEditorRowsFont(ui->patternEditor->getRowsFont().toStdString()); } if (config.lock()->getOrderListHeaderFont().empty()) { config.lock()->setOrderListHeaderFont(ui->orderList->getHeaderFont().toStdString()); } if (config.lock()->getOrderListRowsFont().empty()) { config.lock()->setOrderListRowsFont(ui->orderList->getRowsFont().toStdString()); } ui->patternEditor->setConfiguration(config_.lock()); ui->orderList->setConfiguration(config_.lock()); updateFonts(); ui->orderList->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false); ui->patternEditor->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false); ui->patternEditor->setCore(bt_); ui->orderList->setCore(bt_); ColorPaletteHandler::loadPalette(palette_); ui->patternEditor->setColorPallete(palette_); ui->orderList->setColorPallete(palette_); updateInstrumentListColors(); ui->waveVisual->setColorPalette(palette_); setMidiConfiguration(); /* Command stack */ QObject::connect(comStack_.get(), &QUndoStack::indexChanged, this, [&](int idx) { setWindowModified(idx || isModifiedForNotCommand_); ui->actionUndo->setEnabled(comStack_->canUndo()); ui->actionRedo->setEnabled(comStack_->canRedo()); }); /* File history */ FileHistoryHandler::loadFileHistory(fileHistory_); for (size_t i = 0; i < fileHistory_->size(); ++i) { // Leave Before Qt5.7.0 style due to windows xp QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i))); action->setData(fileHistory_->at(i)); } QObject::connect(ui->menu_Recent_Files, &QMenu::triggered, this, [&](QAction* action) { if (action != ui->actionClear) { if (isWindowModified()) { auto modTitleStd = bt_->getModuleTitle(); QString modTitle = QString::fromUtf8(modTitleStd.c_str(), static_cast(modTitleStd.length())); if (modTitle.isEmpty()) modTitle = tr("Untitled"); QMessageBox dialog(QMessageBox::Warning, "BambooTracker", tr("Save changes to %1?").arg(modTitle), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (dialog.exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } openModule(action->data().toString()); } }); /* Sub tool bar */ auto octLab = new QLabel(tr("Octave")); octLab->setMargin(6); ui->subToolBar->addWidget(octLab); octave_ = new QSpinBox(); octave_->setMinimum(0); octave_->setMaximum(7); octave_->setValue(bt_->getCurrentOctave()); auto octFunc = [&](int octave) { bt_->setCurrentOctave(octave); }; // Leave Before Qt5.7.0 style due to windows xp QObject::connect(octave_, static_cast(&QSpinBox::valueChanged), this, octFunc); ui->subToolBar->addWidget(octave_); ui->subToolBar->addSeparator(); ui->subToolBar->addAction(ui->actionFollow_Mode); ui->subToolBar->addSeparator(); auto hlLab1 = new QLabel(tr("Step highlight 1st")); hlLab1->setMargin(6); ui->subToolBar->addWidget(hlLab1); highlight1_ = new QSpinBox(); highlight1_->setMinimum(1); highlight1_->setMaximum(256); highlight1_->setValue(8); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(highlight1_, static_cast(&QSpinBox::valueChanged), this, [&](int count) { bt_->setModuleStepHighlight1Distance(static_cast(count)); ui->patternEditor->setPatternHighlight1Count(count); }); ui->subToolBar->addWidget(highlight1_); auto hlLab2 = new QLabel(tr("2nd")); hlLab2->setMargin(6); ui->subToolBar->addWidget(hlLab2); highlight2_ = new QSpinBox(); highlight2_->setMinimum(1); highlight2_->setMaximum(256); highlight2_->setValue(8); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(highlight2_, static_cast(&QSpinBox::valueChanged), this, [&](int count) { bt_->setModuleStepHighlight2Distance(static_cast(count)); ui->patternEditor->setPatternHighlight2Count(count); }); ui->subToolBar->addWidget(highlight2_); /* Splitter */ ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); /* Module settings */ QObject::connect(ui->modTitleLineEdit, &QLineEdit::textEdited, this, [&](QString str) { bt_->setModuleTitle(str.toUtf8().toStdString()); setModifiedTrue(); setWindowTitle(); }); QObject::connect(ui->authorLineEdit, &QLineEdit::textEdited, this, [&](QString str) { bt_->setModuleAuthor(str.toUtf8().toStdString()); setModifiedTrue(); }); QObject::connect(ui->copyrightLineEdit, &QLineEdit::textEdited, this, [&](QString str) { bt_->setModuleCopyright(str.toUtf8().toStdString()); setModifiedTrue(); }); /* Edit settings */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->editableStepSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int n) { ui->patternEditor->setEditableStep(n); config.lock()->setEditableStep(static_cast(n)); }); ui->editableStepSpinBox->setValue(static_cast(config.lock()->getEditableStep())); ui->patternEditor->setEditableStep(static_cast(config.lock()->getEditableStep())); ui->keyRepeatCheckBox->setCheckState(config.lock()->getKeyRepetition() ? Qt::Checked : Qt::Unchecked); /* Song */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->songComboBox, static_cast(&QComboBox::currentIndexChanged), this, [&](int num) { if (num == -1) return; freezeViews(); if (!timer_) stream_->stop(); bt_->setCurrentSongNumber(num); loadSong(); if (!timer_) stream_->start(); }); /* Song settings */ // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->tempoSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int tempo) { int curSong = bt_->getCurrentSongNumber(); if (tempo != bt_->getSongTempo(curSong)) { bt_->setSongTempo(curSong, tempo); setModifiedTrue(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->speedSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int speed) { int curSong = bt_->getCurrentSongNumber(); if (speed != bt_->getSongSpeed(curSong)) { bt_->setSongSpeed(curSong, speed); setModifiedTrue(); } }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->patternSizeSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int size) { bt_->setDefaultPatternSize(bt_->getCurrentSongNumber(), static_cast(size)); ui->patternEditor->onDefaultPatternSizeChanged(); setModifiedTrue(); }); // Leave Before Qt5.7.0 style due to windows xp QObject::connect(ui->grooveSpinBox, static_cast(&QSpinBox::valueChanged), this, [&](int n) { bt_->setSongGroove(bt_->getCurrentSongNumber(), n); setModifiedTrue(); }); /* Instrument list */ ui->instrumentListWidget->setContextMenuPolicy(Qt::CustomContextMenu); // Set core data to editor when add insrument QObject::connect(ui->instrumentListWidget->model(), &QAbstractItemModel::rowsInserted, this, &MainWindow::onInstrumentListWidgetItemAdded); auto instToolBar = new QToolBar(); instToolBar->setIconSize(QSize(16, 16)); instToolBar->addAction(ui->actionNew_Instrument); instToolBar->addAction(ui->actionRemove_Instrument); instToolBar->addAction(ui->actionClone_Instrument); instToolBar->addSeparator(); instToolBar->addAction(ui->actionLoad_From_File); instToolBar->addAction(ui->actionSave_To_File); instToolBar->addSeparator(); instToolBar->addAction(ui->actionEdit); instToolBar->addSeparator(); instToolBar->addAction(ui->actionRename_Instrument); ui->instrumentListGroupBox->layout()->addWidget(instToolBar); ui->instrumentListWidget->installEventFilter(this); /* Pattern editor */ ui->patternEditor->setCommandStack(comStack_); ui->patternEditor->installEventFilter(this); QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged, ui->orderList, &OrderListEditor::setCurrentTrack); QObject::connect(ui->patternEditor, &PatternEditor::currentOrderChanged, ui->orderList, &OrderListEditor::setCurrentOrder); QObject::connect(ui->patternEditor, &PatternEditor::focusIn, this, &MainWindow::updateMenuByPattern); QObject::connect(ui->patternEditor, &PatternEditor::selected, this, &MainWindow::updateMenuByPatternAndOrderSelection); QObject::connect(ui->patternEditor, &PatternEditor::returnPressed, this, [&] { if (bt_->isPlaySong()) stopPlaySong(); else startPlaySong(); }); QObject::connect(ui->patternEditor, &PatternEditor::instrumentEntered, this, [&](int num) { auto list = ui->instrumentListWidget; if (num != -1) { for (int i = 0; i < list->count(); ++i) { if (list->item(i)->data(Qt::UserRole).toInt() == num) { list->setCurrentRow(i); return ; } } } }); QObject::connect(ui->patternEditor, &PatternEditor::effectEntered, this, [&](QString text) { statusDetail_->setText(text); }); /* Order List */ ui->orderList->setCommandStack(comStack_); ui->orderList->installEventFilter(this); QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged, ui->patternEditor, &PatternEditor::setCurrentTrack); QObject::connect(ui->orderList, &OrderListEditor::currentOrderChanged, ui->patternEditor, &PatternEditor::setCurrentOrder); QObject::connect(ui->orderList, &OrderListEditor::orderEdited, ui->patternEditor, &PatternEditor::onOrderListEdited); QObject::connect(ui->orderList, &OrderListEditor::focusIn, this, &MainWindow::updateMenuByOrder); QObject::connect(ui->orderList, &OrderListEditor::selected, this, &MainWindow::updateMenuByPatternAndOrderSelection); QObject::connect(ui->orderList, &OrderListEditor::returnPressed, this, [&] { if (bt_->isPlaySong()) stopPlaySong(); else startPlaySong(); }); /* Visuals */ visualTimer_.reset(new QTimer); visualTimer_->start(40); QObject::connect(visualTimer_.get(), &QTimer::timeout, this, &MainWindow::updateVisuals); /* Status bar */ statusDetail_ = new QLabel(); statusDetail_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); statusStyle_ = new QLabel(); statusStyle_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusInst_ = new QLabel(); statusInst_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusOctave_ = new QLabel(); statusOctave_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusIntr_ = new QLabel(); statusIntr_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusMixer_ = new QLabel(); statusMixer_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusBpm_ = new QLabel(); statusBpm_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); statusPlayPos_ = new QLabel(); statusPlayPos_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); ui->statusBar->addWidget(statusDetail_, 4); ui->statusBar->addPermanentWidget(statusStyle_, 1); ui->statusBar->addPermanentWidget(statusInst_, 1); ui->statusBar->addPermanentWidget(statusOctave_, 1); ui->statusBar->addPermanentWidget(statusIntr_, 1); ui->statusBar->addPermanentWidget(statusMixer_, 1); ui->statusBar->addPermanentWidget(statusBpm_, 1); ui->statusBar->addPermanentWidget(statusPlayPos_, 1); statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave())); statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz")); /* Clipboard */ QObject::connect(QApplication::clipboard(), &QClipboard::dataChanged, this, [&]() { if (isEditedOrder_) updateMenuByOrder(); else if (isEditedPattern_) updateMenuByPattern(); }); /* MIDI */ midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)"); Q_ASSERT(midiKeyEventMethod_ != -1); midiProgramEventMethod_ = metaObject()->indexOfSlot("midiProgramEvent(uchar,uchar)"); Q_ASSERT(midiProgramEventMethod_ != -1); MidiInterface::instance().installInputHandler(&midiThreadReceivedEvent, this); /* Audio stream */ bool savedDeviceExists = false; for (QAudioDeviceInfo audioDevice : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { if (audioDevice.deviceName().toUtf8().toStdString() == config.lock()->getSoundDevice()) { savedDeviceExists = true; break; } } if (!savedDeviceExists) { QString sndDev = QAudioDeviceInfo::defaultOutputDevice().deviceName(); config.lock()->setSoundDevice(sndDev.toUtf8().toStdString()); } stream_ = std::make_shared(); stream_->setTickUpdateCallback(+[](void* cbPtr) -> int { auto bt = reinterpret_cast(cbPtr); return bt->streamCountUp(); }, bt_.get()); stream_->setGenerateCallback(+[](int16_t* container, size_t nSamples, void* cbPtr) { auto bt = reinterpret_cast(cbPtr); bt->getStreamSamples(container, nSamples); }, bt_.get()); QObject::connect(stream_.get(), &AudioStream::streamInterrupted, this, &MainWindow::onNewTickSignaled); bool streamState = stream_->initialize( static_cast(bt_->getStreamRate()), static_cast(bt_->getStreamDuration()), bt_->getModuleTickFrequency(), QString::fromUtf8(config.lock()->getSoundAPI().c_str(), static_cast(config.lock()->getSoundAPI().length())), QString::fromUtf8(config.lock()->getSoundDevice().c_str(), static_cast(config.lock()->getSoundDevice().length()))); if (!streamState) showStreamFailedDialog(); RealChipInterface intf = config.lock()->getRealChipInterface(); if (intf == RealChipInterface::NONE) { bt_->useSCCI(nullptr); bt_->useC86CTL(nullptr); } else { timer_ = std::make_unique(); timer_->setInterval(1000000 / bt_->getModuleTickFrequency()); tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()"); Q_ASSERT(tickEventMethod_ != -1); timer_->setFunction([&]{ QMetaMethod method = this->metaObject()->method(this->tickEventMethod_); method.invoke(this, Qt::QueuedConnection); }); setRealChipInterface(intf); timer_->start(); } /* Load module */ if (filePath.isEmpty()) { loadModule(); setInitialSelectedInstrument(); if (!timer_) stream_->start(); } else { openModule(filePath); // If use emulation, stream stars } } MainWindow::~MainWindow() { MidiInterface::instance().uninstallInputHandler(&midiThreadReceivedEvent, this); stream_->shutdown(); } bool MainWindow::eventFilter(QObject *watched, QEvent *event) { if (auto fmForm = qobject_cast(watched)) { // Change current instrument by activating FM editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(fmForm->getInstrumentNumber()); ui->instrumentListWidget->setCurrentRow(row); return false; } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentFMWindowWidth(fmForm->width()); config_.lock()->setInstrumentFMWindowHeight(fmForm->height()); return false; } } if (auto ssgForm = qobject_cast(watched)) { // Change current instrument by activating SSG editor if (event->type() == QEvent::WindowActivate) { int row = findRowFromInstrumentList(ssgForm->getInstrumentNumber()); ui->instrumentListWidget->setCurrentRow(row); return false; } else if (event->type() == QEvent::Resize) { config_.lock()->setInstrumentSSGWindowWidth(ssgForm->width()); config_.lock()->setInstrumentSSGWindowHeight(ssgForm->height()); return false; } } if (watched == ui->instrumentListWidget) { switch (event->type()) { case QEvent::KeyPress: if (dynamic_cast(event)->key() == Qt::Key_Insert) addInstrument(); break; case QEvent::FocusIn: updateMenuByInstrumentList(); break; default: break; } } return false; } void MainWindow::showEvent(QShowEvent* event) { Q_UNUSED(event) if (!hasShownOnce_) { int y = config_.lock()->getMainWindowVerticalSplit(); if (y == -1) { config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front()); } else { ui->splitter->setSizes({ y, ui->splitter->height() - ui->splitter->handleWidth() - y }); } hasShownOnce_ = true; } } void MainWindow::keyPressEvent(QKeyEvent *event) { int key = event->key(); /* Key check */ QString seq = QKeySequence(static_cast(event->modifiers()) | key).toString(); if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveUpKey().c_str(), static_cast(config_.lock()->getOctaveUpKey().length()))).toString()) { changeOctave(true); return; } else if (seq == QKeySequence( QString::fromUtf8(config_.lock()->getOctaveDownKey().c_str(), static_cast(config_.lock()->getOctaveDownKey().length()))).toString()) { changeOctave(false); return; } /* General keys */ switch (key) { case Qt::Key_F2: ui->patternEditor->setFocus(); return; case Qt::Key_F3: ui->orderList->setFocus(); return; case Qt::Key_F4: ui->instrumentListWidget->setFocus(); updateMenuByInstrumentList(); return; default: if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(key); try { bt_->jamKeyOn(getJamKeyFromLayoutMapping(qtKey, config_)); } catch (std::invalid_argument&) {} } break; } } void MainWindow::keyReleaseEvent(QKeyEvent *event) { int key = event->key(); if (!event->isAutoRepeat()) { // Musical keyboard Qt::Key qtKey = static_cast(key); try { bt_->jamKeyOff(getJamKeyFromLayoutMapping(qtKey, config_)); } catch (std::invalid_argument&) {} } } void MainWindow::dragEnterEvent(QDragEnterEvent* event) { auto mime = event->mimeData(); if (mime->hasUrls() && mime->urls().length() == 1) { switch (FileIO::judgeFileTypeFromExtension( QFileInfo(mime->urls().first().toLocalFile()).suffix().toStdString())) { case FileIO::FileType::Mod: case FileIO::FileType::Inst: case FileIO::FileType::Bank: event->acceptProposedAction(); break; default: break; } } } void MainWindow::dropEvent(QDropEvent* event) { QString file = event->mimeData()->urls().first().toLocalFile(); switch (FileIO::judgeFileTypeFromExtension(QFileInfo(file).suffix().toStdString())) { case FileIO::FileType::Mod: { if (isWindowModified()) { auto modTitleStd = bt_->getModuleTitle(); QString modTitle = QString::fromUtf8(modTitleStd.c_str(), static_cast(modTitleStd.length())); if (modTitle.isEmpty()) modTitle = tr("Untitled"); QMessageBox dialog(QMessageBox::Warning, "BambooTracker", tr("Save changes to %1?").arg(modTitle), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (dialog.exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } bt_->stopPlaySong(); lockWidgets(false); openModule(file); break; } case FileIO::FileType::Inst: { funcLoadInstrument(file); break; } case FileIO::FileType::Bank: { funcImportInstrumentsFromBank(file); break; } default: break; } } void MainWindow::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (!isMaximized()) { // Check previous size config_.lock()->setMainWindowWidth(event->oldSize().width()); config_.lock()->setMainWindowHeight(event->oldSize().height()); } } void MainWindow::moveEvent(QMoveEvent* event) { QWidget::moveEvent(event); if (!isMaximized()) { // Check previous position config_.lock()->setMainWindowX(event->oldPos().x()); config_.lock()->setMainWindowY(event->oldPos().y()); } } void MainWindow::closeEvent(QCloseEvent *event) { if (isWindowModified()) { auto modTitleStd = bt_->getModuleTitle(); QString modTitle = QString::fromUtf8(modTitleStd.c_str(), static_cast(modTitleStd.length())); if (modTitle.isEmpty()) modTitle = tr("Untitled"); QMessageBox dialog(QMessageBox::Warning, "BambooTracker", tr("Save changes to %1?").arg(modTitle), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (dialog.exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: event->ignore(); return; default: break; } } if (isMaximized()) { config_.lock()->setMainWindowMaximized(true); } else { config_.lock()->setMainWindowMaximized(false); config_.lock()->setMainWindowWidth(width()); config_.lock()->setMainWindowHeight(height()); config_.lock()->setMainWindowX(x()); config_.lock()->setMainWindowY(y()); } config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front()); config_.lock()->setFollowMode(bt_->isFollowPlay()); instForms_->closeAll(); FileHistoryHandler::saveFileHistory(fileHistory_); event->accept(); } void MainWindow::freezeViews() { ui->orderList->freeze(); ui->patternEditor->freeze(); } void MainWindow::updateInstrumentListColors() { ui->instrumentListWidget->setStyleSheet( QString("QListWidget { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:hover { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistHovBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:selected { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistSelBackColor.name(QColor::HexArgb)) + QString("QListWidget::item:selected:hover { color: %1; background: %2; }") .arg(palette_->ilistTextColor.name(QColor::HexArgb), palette_->ilistHovSelBackColor.name(QColor::HexArgb))); } /********** MIDI **********/ void MainWindow::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData) { MainWindow *self = reinterpret_cast(userData); Q_UNUSED(delay) // Note-On/Note-Off if (len == 3 && (msg[0] & 0xe0) == 0x80) { uint8_t status = msg[0]; uint8_t key = msg[1]; uint8_t velocity = msg[2]; QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity)); } // Program change else if (len == 2 && (msg[0] & 0xf0) == 0xc0) { uint8_t status = msg[0]; uint8_t program = msg[1]; QMetaMethod method = self->metaObject()->method(self->midiProgramEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, program)); } } void MainWindow::midiKeyEvent(uchar status, uchar key, uchar velocity) { bool release = ((status & 0xf0) == 0x80) || velocity == 0; int k = static_cast(key) - 12; octave_->setValue(k / 12); int n = instForms_->checkActivatedFormNumber(); if (n == -1) { bt_->jamKeyOff(k); // possibility to recover on stuck note if (!release) bt_->jamKeyOn(k); } else { SoundSource src = instForms_->getFormInstrumentSoundSource(n); bt_->jamKeyOffForced(k, src); // possibility to recover on stuck note if (!release) bt_->jamKeyOnForced(k, src); } } void MainWindow::midiProgramEvent(uchar status, uchar program) { Q_UNUSED(status) int row = findRowFromInstrumentList(program); ui->instrumentListWidget->setCurrentRow(row); } /********** Instrument list **********/ void MainWindow::addInstrument() { switch (bt_->getCurrentTrackAttribute().source) { case SoundSource::FM: case SoundSource::SSG: { auto& list = ui->instrumentListWidget; int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; // Maximum count check QString name = tr("Instrument %1").arg(num); bt_->addInstrument(num, name.toUtf8().toStdString()); TrackAttribute attrib = bt_->getCurrentTrackAttribute(); comStack_->push(new AddInstrumentQtCommand(list, num, name, attrib.source, instForms_)); ui->instrumentListWidget->setCurrentRow(num); break; } case SoundSource::DRUM: break; } } void MainWindow::removeInstrument(int row) { if (row < 0) return; auto& list = ui->instrumentListWidget; int num = list->item(row)->data(Qt::UserRole).toInt(); bt_->removeInstrument(num); comStack_->push(new RemoveInstrumentQtCommand(list, num, row, instForms_)); } void MainWindow::editInstrument() { auto item = ui->instrumentListWidget->currentItem(); int num = item->data(Qt::UserRole).toInt(); instForms_->showForm(num); } int MainWindow::findRowFromInstrumentList(int instNum) { auto& list = ui->instrumentListWidget; int row = 0; for (; row < list->count(); ++row) { auto item = list->item(row); if (item->data(Qt::UserRole).toInt() == instNum) break; } return row; } void MainWindow::renameInstrument() { auto list = ui->instrumentListWidget; auto item = list->currentItem(); int num = item->data(Qt::UserRole).toInt(); QString oldName = instForms_->getFormInstrumentName(num); auto line = new QLineEdit(oldName); QObject::connect(line, &QLineEdit::editingFinished, this, [&, item, list, num, oldName] { QString newName = qobject_cast(list->itemWidget(item))->text(); list->removeItemWidget(item); bt_->setInstrumentName(num, newName.toUtf8().toStdString()); int row = findRowFromInstrumentList(num); comStack_->push(new ChangeInstrumentNameQtCommand(list, num, row, instForms_, oldName, newName)); }); ui->instrumentListWidget->setItemWidget(item, line); line->selectAll(); line->setFocus(); } void MainWindow::cloneInstrument() { int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; int refNum = ui->instrumentListWidget->currentItem()->data(Qt::UserRole).toInt(); // KEEP CODE ORDER // bt_->cloneInstrument(num, refNum); comStack_->push(new CloneInstrumentQtCommand( ui->instrumentListWidget, num, refNum, instForms_)); //----------// } void MainWindow::deepCloneInstrument() { int num = bt_->findFirstFreeInstrumentNumber(); if (num == -1) return; int refNum = ui->instrumentListWidget->currentItem()->data(Qt::UserRole).toInt(); // KEEP CODE ORDER // bt_->deepCloneInstrument(num, refNum); comStack_->push(new DeepCloneInstrumentQtCommand( ui->instrumentListWidget, num, refNum, instForms_)); //----------// } void MainWindow::loadInstrument() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QStringList filters { tr("BambooTracker instrument (*.bti)"), tr("DefleMask preset (*.dmp)"), tr("TFM Music Maker instrument (*.tfi)"), tr("VGM Music Maker instrument (*.vgi)"), tr("WOPN instrument (*.opni)"), tr("Gens KMod dump (*.y12)"), tr("MVSTracker instrument (*.ins)") }; QString defaultFilter = filters.at(config_.lock()->getInstrumentOpenFormat()); QString file = QFileDialog::getOpenFileName(this, tr("Open instrument"), (dir.isEmpty() ? "./" : dir), filters.join(";;"), &defaultFilter); if (file.isNull()) return; int index = getSelectedFileFilter(file, filters); if (index != -1) config_.lock()->setInstrumentOpenFormat(index); funcLoadInstrument(file); } void MainWindow::funcLoadInstrument(QString file) { int n = bt_->findFirstFreeInstrumentNumber(); if (n == -1) { showFileIOErrorDialog(FileInputError(FileIO::FileType::Inst), tr( "The number of instruments has reached the upper limit.")); } try { QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) throw FileInputError(FileIO::FileType::Inst); QByteArray array = fp.readAll(); fp.close(); BinaryContainer contaner; contaner.appendVector(std::vector(array.begin(), array.end())); bt_->loadInstrument(contaner, file.toStdString(), n); auto inst = bt_->getInstrument(n); auto name = inst->getName(); comStack_->push(new AddInstrumentQtCommand( ui->instrumentListWidget, n, QString::fromUtf8(name.c_str(), static_cast(name.length())), inst->getSoundSource(), instForms_)); ui->instrumentListWidget->setCurrentRow(n); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileInputError(FileIO::FileType::Inst), "\n" + QString(e.what())); } } void MainWindow::saveInstrument() { int n = ui->instrumentListWidget->currentItem()->data(Qt::UserRole).toInt(); auto nameStd = bt_->getInstrument(n)->getName(); QString name = QString::fromUtf8(nameStd.c_str(), static_cast(nameStd.length())); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getSaveFileName( this, tr("Save instrument"), QString("%1/%2.bti").arg(dir.isEmpty() ? "." : dir, name), tr("BambooTracker instrument (*.bti)")); if (file.isNull()) return; if (!file.endsWith(".bti")) file += ".bti"; // For linux try { BinaryContainer container; bt_->saveInstrument(container, n); QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::Inst); fp.write(container.getPointer(), container.size()); fp.close(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::Inst), "\n" + QString(e.what())); } } void MainWindow::importInstrumentsFromBank() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QStringList filters { tr("BambooTracker bank (*.btb)"), tr("WOPN bank (*.wopn)") }; QString defaultFilter = filters.at(config_.lock()->getBankOpenFormat()); QString file = QFileDialog::getOpenFileName(this, tr("Open bank"), (dir.isEmpty() ? "./" : dir), filters.join(";;"), &defaultFilter); if (file.isNull()) { return; } else { int index = getSelectedFileFilter(file, filters); if (index != -1) config_.lock()->setBankOpenFormat(index); } funcImportInstrumentsFromBank(file); } void MainWindow::funcImportInstrumentsFromBank(QString file) { std::unique_ptr bank; try { QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) throw FileInputError(FileIO::FileType::Bank); QByteArray array = fp.readAll(); fp.close(); BinaryContainer container; container.appendVector(std::vector(array.begin(), array.end())); bank.reset(BankIO::loadBank(container, file.toStdString())); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileInputError(FileIO::FileType::Bank), "\n" + QString(e.what())); return; } InstrumentSelectionDialog dlg(*bank, tr("Select instruments to load:"), this); if (dlg.exec() != QDialog::Accepted) return; QVector selection = dlg.currentInstrumentSelection(); if (selection.empty()) return; try { int lastNum = ui->instrumentListWidget->currentRow(); for (size_t index : selection) { int n = bt_->findFirstFreeInstrumentNumber(); if (n == -1){ showFileIOErrorDialog(FileInputError(FileIO::FileType::Inst), tr( "The number of instruments has reached the upper limit.")); ui->instrumentListWidget->setCurrentRow(lastNum); return; } bt_->importInstrument(*bank, index, n); auto inst = bt_->getInstrument(n); auto name = inst->getName(); comStack_->push(new AddInstrumentQtCommand( ui->instrumentListWidget, n, QString::fromUtf8(name.c_str(), static_cast(name.length())), inst->getSoundSource(), instForms_)); lastNum = n; } ui->instrumentListWidget->setCurrentRow(lastNum); } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileInputError(FileIO::FileType::Bank), "\n" + QString(e.what())); } } void MainWindow::exportInstrumentsToBank() { std::vector ids = bt_->getInstrumentIndices(); std::shared_ptr bank(std::make_shared(ids, bt_->getInstrumentNames())); InstrumentSelectionDialog dlg(*bank, tr("Select instruments to save:"), this); if (dlg.exec() != QDialog::Accepted) return; QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getSaveFileName(this, tr("Save bank"), (dir.isEmpty() ? "./" : dir), tr("BambooTracker bank (*.btb)")); if (file.isNull()) return; QVector selection = dlg.currentInstrumentSelection(); if (selection.empty()) return; std::vector sel; std::transform(selection.begin(), selection.end(), std::back_inserter(sel), [&ids](size_t i) { return ids.at(i); }); std::sort(sel.begin(), sel.end()); try { BinaryContainer container; bt_->exportInstruments(container, sel); QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::Bank); fp.write(container.getPointer(), container.size()); fp.close(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::Bank), "\n" + QString(e.what())); } } /********** Undo-Redo **********/ void MainWindow::undo() { bt_->undo(); comStack_->undo(); } void MainWindow::redo() { bt_->redo(); comStack_->redo(); } /********** Load data **********/ void MainWindow::loadModule() { instForms_->clearAll(); ui->instrumentListWidget->clear(); on_instrumentListWidget_itemSelectionChanged(); auto modTitle = bt_->getModuleTitle(); ui->modTitleLineEdit->setText( QString::fromUtf8(modTitle.c_str(), static_cast(modTitle.length()))); ui->modTitleLineEdit->setCursorPosition(0); auto modAuthor = bt_->getModuleAuthor(); ui->authorLineEdit->setText( QString::fromUtf8(modAuthor.c_str(), static_cast(modAuthor.length()))); ui->authorLineEdit->setCursorPosition(0); auto modCopyright = bt_->getModuleCopyright(); ui->copyrightLineEdit->setText( QString::fromUtf8(modCopyright.c_str(), static_cast(modCopyright.length()))); ui->copyrightLineEdit->setCursorPosition(0); ui->songComboBox->clear(); for (size_t i = 0; i < bt_->getSongCount(); ++i) { std::string srcTitle = bt_->getSongTitle(static_cast(i)); QString title = QString::fromUtf8(srcTitle.c_str(), static_cast(srcTitle.length())); if (title.isEmpty()) title = tr("Untitled"); ui->songComboBox->addItem(QString("#%1 %2").arg(i).arg(title)); } highlight1_->setValue(static_cast(bt_->getModuleStepHighlight1Distance())); highlight2_->setValue(static_cast(bt_->getModuleStepHighlight2Distance())); for (auto& idx : bt_->getInstrumentIndices()) { auto inst = bt_->getInstrument(idx); auto name = inst->getName(); comStack_->push(new AddInstrumentQtCommand( ui->instrumentListWidget, idx, QString::fromUtf8(name.c_str(), static_cast(name.length())), inst->getSoundSource(), instForms_)); } isSavedModBefore_ = false; loadSong(); // Set tick frequency stream_->setInterruption(bt_->getModuleTickFrequency()); if (timer_) timer_->setInterval(1000000 / bt_->getModuleTickFrequency()); statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz")); // Set mixer QString text; switch (bt_->getModuleMixerType()) { case MixerType::UNSPECIFIED: bt_->setMasterVolumeFM(config_.lock()->getMixerVolumeFM()); bt_->setMasterVolumeSSG(config_.lock()->getMixerVolumeSSG()); text = tr("-"); break; case MixerType::CUSTOM: bt_->setMasterVolumeFM(bt_->getModuleCustomMixerFMLevel()); bt_->setMasterVolumeSSG(bt_->getModuleCustomMixerSSGLevel()); text = tr("Custom"); break; case MixerType::PC_9821_PC_9801_86: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(-5.5); text = tr("PC-9821 with PC-9801-86"); break; case MixerType::PC_9821_SPEAK_BOARD: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(-3.0); text = tr("PC-9821 with Speak Board"); break; case MixerType::PC_8801_VA2: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(1.5); text = tr("PC-88VA2"); break; case MixerType::PC_8801_MKII_SR: bt_->setMasterVolumeFM(0); bt_->setMasterVolumeSSG(2.5); text = tr("NEC PC-8801mkIISR"); break; } statusMixer_->setText(text); // Clear records QApplication::clipboard()->clear(); comStack_->clear(); bt_->clearCommandHistory(); } void MainWindow::openModule(QString file) { try { freezeViews(); if (!timer_) stream_->stop(); BinaryContainer container; QFile fp(file); if (!fp.open(QIODevice::ReadOnly)) throw FileInputError(FileIO::FileType::Mod); QByteArray array = fp.readAll(); fp.close(); container.appendVector(std::vector(array.begin(), array.end())); bt_->loadModule(container); bt_->setModulePath(file.toStdString()); loadModule(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); changeFileHistory(file); } catch (std::exception& e) { if (auto ef = dynamic_cast(&e)) { showFileIOErrorDialog(*ef); } else { showFileIOErrorDialog(FileInputError(FileIO::FileType::Mod), "\n" + QString(e.what())); } // Init module freezeViews(); bt_->makeNewModule(); loadModule(); } isModifiedForNotCommand_ = false; setWindowModified(false); if (!timer_) stream_->start(); setInitialSelectedInstrument(); } void MainWindow::loadSong() { // Init position int songCnt = static_cast(bt_->getSongCount()); if (ui->songComboBox->currentIndex() >= songCnt) bt_->setCurrentSongNumber(songCnt - 1); else bt_->setCurrentSongNumber(bt_->getCurrentSongNumber()); bt_->setCurrentOrderNumber(0); bt_->setCurrentTrack(0); bt_->setCurrentStepNumber(0); // Init ui ui->orderList->unfreeze(); ui->patternEditor->unfreeze(); ui->orderList->onSongLoaded(); ui->orderListGroupBox->setMaximumWidth( ui->orderListGroupBox->contentsMargins().left() + ui->orderListGroupBox->layout()->contentsMargins().left() + ui->orderList->maximumWidth() + ui->orderListGroupBox->layout()->contentsMargins().right() + ui->orderListGroupBox->contentsMargins().right()); ui->patternEditor->onSongLoaded(); int curSong = bt_->getCurrentSongNumber(); ui->songComboBox->setCurrentIndex(curSong); ui->tempoSpinBox->setValue(bt_->getSongTempo(curSong)); ui->speedSpinBox->setValue(bt_->getSongSpeed(curSong)); ui->patternSizeSpinBox->setValue(static_cast(bt_->getDefaultPatternSize(curSong))); ui->grooveSpinBox->setValue(bt_->getSongGroove(curSong)); ui->grooveSpinBox->setMaximum(static_cast(bt_->getGrooveCount()) - 1); if (bt_->isUsedTempoInSong(curSong)) { ui->tempoSpinBox->setEnabled(true); ui->speedSpinBox->setEnabled(true); ui->grooveCheckBox->setChecked(false); ui->grooveSpinBox->setEnabled(false); } else { ui->tempoSpinBox->setEnabled(false); ui->speedSpinBox->setEnabled(false); ui->grooveCheckBox->setChecked(true); ui->grooveSpinBox->setEnabled(true); } setWindowTitle(); switch (bt_->getSongStyle(bt_->getCurrentSongNumber()).type) { case SongType::Standard: statusStyle_->setText(tr("Standard")); break; case SongType::FM3chExpanded: statusStyle_->setText(tr("FM3ch expanded")); break; } statusPlayPos_->setText(config_.lock()->getShowRowNumberInHex() ? "00/00" : "000/000"); } /********** Play song **********/ void MainWindow::startPlaySong() { bt_->startPlaySong(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayFromStart() { bt_->startPlayFromStart(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayPattern() { bt_->startPlayPattern(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::startPlayFromCurrentStep() { bt_->startPlayFromCurrentStep(); lockWidgets(true); firstViewUpdateRequest_ = true; } void MainWindow::stopPlaySong() { bt_->stopPlaySong(); lockWidgets(false); ui->patternEditor->onStoppedPlaySong(); ui->orderList->onStoppedPlaySong(); } void MainWindow::lockWidgets(bool isLock) { hasLockedWigets_ = isLock; ui->songComboBox->setEnabled(!isLock); } /********** Octave change **********/ void MainWindow::changeOctave(bool upFlag) { if (upFlag) octave_->stepUp(); else octave_->stepDown(); statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave())); } /********** Configuration change **********/ void MainWindow::changeConfiguration() { // Real chip interface RealChipInterface intf = config_.lock()->getRealChipInterface(); if (intf == RealChipInterface::NONE) { timer_.reset(); bt_->useSCCI(nullptr); bt_->useC86CTL(nullptr); bool streamState = stream_->initialize( config_.lock()->getSampleRate(), config_.lock()->getBufferLength(), bt_->getModuleTickFrequency(), QString::fromUtf8(config_.lock()->getSoundAPI().c_str(), static_cast(config_.lock()->getSoundAPI().length())), QString::fromUtf8(config_.lock()->getSoundDevice().c_str(), static_cast(config_.lock()->getSoundDevice().length()))); if (!streamState) showStreamFailedDialog(); stream_->start(); } else { stream_->stop(); if (timer_) { timer_->stop(); } else { timer_ = std::make_unique(); timer_->setInterval(1000000 / bt_->getModuleTickFrequency()); tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()"); Q_ASSERT(tickEventMethod_ != -1); timer_->setFunction([&]{ QMetaMethod method = this->metaObject()->method(this->tickEventMethod_); method.invoke(this, Qt::QueuedConnection); }); } setRealChipInterface(intf); timer_->start(); } setMidiConfiguration(); updateFonts(); ui->orderList->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll()); ui->patternEditor->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll()); instForms_->updateByConfiguration(); bt_->changeConfiguration(config_); ui->waveVisual->setVisible(config_.lock()->getShowWaveVisual()); updateInstrumentListColors(); update(); } void MainWindow::setRealChipInterface(RealChipInterface intf) { if (intf == bt_->getRealChipinterface()) return; if (isWindowModified() && QMessageBox::warning(this, tr("Warning"), tr("The module has been changed. Do you want to save it?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { on_actionSave_As_triggered(); } switch (intf) { case RealChipInterface::SCCI: bt_->useC86CTL(nullptr); scciDll_->load(); if (scciDll_->isLoaded()) { auto getManager = reinterpret_cast( scciDll_->resolve("getSoundInterfaceManager")); bt_->useSCCI(getManager ? getManager() : nullptr); } else { bt_->useSCCI(nullptr); } break; case RealChipInterface::C86CTL: bt_->useSCCI(nullptr); c86ctlDll_->load(); if (c86ctlDll_->isLoaded()) { bt_->useC86CTL(new C86ctlBase(c86ctlDll_->resolve("CreateInstance"))); } else { bt_->useC86CTL(nullptr); } break; default: break; } } void MainWindow::setMidiConfiguration() { MidiInterface &midiIntf = MidiInterface::instance(); std::string midiInPortName = config_.lock()->getMidiInputPort(); if (!midiIntf.hasInitializedInput()) { QMessageBox::critical(this, tr("Error"), tr("Could not initialize MIDI input."), QMessageBox::Ok, QMessageBox::Ok); } if (!midiInPortName.empty()) midiIntf.openInputPortByName(midiInPortName); else if (midiIntf.supportsVirtualPort()) midiIntf.openInputPort(~0u); } void MainWindow::updateFonts() { ui->patternEditor->setFonts( QString::fromUtf8(config_.lock()->getPatternEditorHeaderFont().c_str(), static_cast(config_.lock()->getPatternEditorHeaderFont().length())), config_.lock()->getPatternEditorHeaderFontSize(), QString::fromUtf8(config_.lock()->getPatternEditorRowsFont().c_str(), static_cast(config_.lock()->getPatternEditorRowsFont().length())), config_.lock()->getPatternEditorRowsFontSize()); ui->orderList->setFonts( QString::fromUtf8(config_.lock()->getOrderListHeaderFont().c_str(), static_cast(config_.lock()->getOrderListHeaderFont().length())), config_.lock()->getOrderListHeaderFontSize(), QString::fromUtf8(config_.lock()->getOrderListRowsFont().c_str(), static_cast(config_.lock()->getOrderListRowsFont().length())), config_.lock()->getOrderListRowsFontSize()); } /********** History change **********/ void MainWindow::changeFileHistory(QString file) { fileHistory_->addFile(file); for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i) ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i)); for (size_t i = 0; i < fileHistory_->size(); ++i) { // Leave Before Qt5.7.0 style due to windows xp QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i))); action->setData(fileHistory_->at(i)); } } /********** Backup **********/ bool MainWindow::backupModule(QString srcFile) { if (!isSavedModBefore_ && config_.lock()->getBackupModules()) { bool err = false; QString backup = srcFile + ".bak"; if (QFile::exists(backup)) err = !QFile::remove(backup); if (err || !QFile::copy(srcFile, backup)) { QMessageBox::critical(this, tr("Error"), tr("Failed to backup module.")); return false; } } return true; } /******************************/ void MainWindow::setWindowTitle() { int n = bt_->getCurrentSongNumber(); auto filePathStd = bt_->getModulePath(); auto songTitleStd = bt_->getSongTitle(n); QString filePath = QString::fromStdString(filePathStd); QString fileName = filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).fileName(); QString songTitle = QString::fromUtf8(songTitleStd.c_str(), static_cast(songTitleStd.length())); if (songTitle.isEmpty()) songTitle = tr("Untitled"); QMainWindow::setWindowTitle(QString("%1[*] [#%2 %3] - BambooTracker") .arg(fileName).arg(QString::number(n)).arg(songTitle)); } void MainWindow::setModifiedTrue() { isModifiedForNotCommand_ = true; setWindowModified(true); } void MainWindow::setInitialSelectedInstrument() { if (bt_->getInstrumentIndices().empty()) { bt_->setCurrentInstrument(-1); statusInst_->setText(tr("No instrument")); } else { ui->instrumentListWidget->setCurrentRow(0); } } QString MainWindow::getModuleFileBaseName() const { auto filePathStd = bt_->getModulePath(); QString filePath = QString::fromStdString(filePathStd); return (filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).completeBaseName()); } int MainWindow::getSelectedFileFilter(QString& file, QStringList& filters) const { QRegularExpression re(R"(\(\*\.(.+)\))"); QString ex = QFileInfo(file).suffix(); for (int i = 0; i < filters.size(); ++i) if (ex == re.match(filters[i]).captured(1)) return i; return -1; } void MainWindow::showFileIOErrorDialog(const FileIOError& e, const QString sub) { const FileIOError *err = &e; QString text, type; switch (err->getFileType()) { case FileIO::FileType::Mod: type = tr("module"); break; case FileIO::FileType::S98: type = tr("s98"); break; case FileIO::FileType::VGM: type = tr("vgm"); break; case FileIO::FileType::WAV: type = tr("wav"); break; case FileIO::FileType::Bank: type = tr("bank"); break; case FileIO::FileType::Inst: type = tr("instrument"); break; default: break; } if (dynamic_cast(err)) { text = tr("Failed to load the %1.").arg(type); } else if (dynamic_cast(err)) { switch (err->getFileType()) { case FileIO::FileType::S98: case FileIO::FileType::VGM: case FileIO::FileType::WAV: text = tr("Failed to export to %1."); break; default: text = tr("Failed to save the %1."); break; } text = text.arg(type); } else if (dynamic_cast(err)) { text = tr("Could not load the %1 properly. " "Please make sure that you have the latest version of BambooTracker.").arg(type); } else if (dynamic_cast(err)) { text = tr("Could not load the %1. It may be corrupted.").arg(type); } QMessageBox::critical(this, tr("Error"), text + sub); } /******************************/ /********** Instrument list events **********/ void MainWindow::on_instrumentListWidget_customContextMenuRequested(const QPoint &pos) { auto& list = ui->instrumentListWidget; QPoint globalPos = list->mapToGlobal(pos); QMenu menu; // Leave Before Qt5.7.0 style due to windows xp menu.addAction(ui->actionNew_Instrument); menu.addAction(ui->actionRemove_Instrument); menu.addSeparator(); menu.addAction(ui->actionRename_Instrument); menu.addSeparator(); menu.addAction(ui->actionClone_Instrument); menu.addAction(ui->actionDeep_Clone_Instrument); menu.addSeparator(); menu.addAction(ui->actionLoad_From_File); menu.addAction(ui->actionSave_To_File); menu.addSeparator(); menu.addAction(ui->actionImport_From_Bank_File); menu.addAction(ui->actionExport_To_Bank_File); menu.addSeparator(); menu.addAction(ui->actionEdit); menu.exec(globalPos); } void MainWindow::on_instrumentListWidget_itemDoubleClicked(QListWidgetItem *item) { Q_UNUSED(item) editInstrument(); } void MainWindow::onInstrumentListWidgetItemAdded(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent) Q_UNUSED(end) // Set core data to editor when add insrument int n = ui->instrumentListWidget->item(start)->data(Qt::UserRole).toInt(); auto& form = instForms_->getForm(n); auto playFunc = [&](int stat) { switch (stat) { case -1: stopPlaySong(); break; case 0: startPlaySong(); break; case 1: startPlayFromStart(); break; case 2: startPlayPattern(); break; case 3: startPlayFromCurrentStep(); break; default: break; } }; switch (instForms_->getFormInstrumentSoundSource(n)) { case SoundSource::FM: { auto fmForm = qobject_cast(form.get()); fmForm->setCore(bt_); fmForm->setConfiguration(config_.lock()); fmForm->setColorPalette(palette_); fmForm->resize(config_.lock()->getInstrumentFMWindowWidth(), config_.lock()->getInstrumentFMWindowHeight()); QObject::connect(fmForm, &InstrumentEditorFMForm::envelopeNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeNumberChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::envelopeParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeParameterChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::lfoNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMLFONumberChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::lfoParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMLFOParameterChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::operatorSequenceNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceNumberChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::operatorSequenceParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceParameterChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::arpeggioNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioNumberChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::arpeggioParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioParameterChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::pitchNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchNumberChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::pitchParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchParameterChanged); QObject::connect(fmForm, &InstrumentEditorFMForm::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::FM); }, Qt::DirectConnection); QObject::connect(fmForm, &InstrumentEditorFMForm::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::FM); }, Qt::DirectConnection); QObject::connect(fmForm, &InstrumentEditorFMForm::octaveChanged, this, &MainWindow::changeOctave, Qt::DirectConnection); QObject::connect(fmForm, &InstrumentEditorFMForm::modified, this, &MainWindow::setModifiedTrue); QObject::connect(fmForm, &InstrumentEditorFMForm::playStatusChanged, this, playFunc); fmForm->installEventFilter(this); instForms_->onInstrumentFMEnvelopeNumberChanged(); instForms_->onInstrumentFMLFONumberChanged(); instForms_->onInstrumentFMOperatorSequenceNumberChanged(); instForms_->onInstrumentFMArpeggioNumberChanged(); instForms_->onInstrumentFMPitchNumberChanged(); break; } case SoundSource::SSG: { auto ssgForm = qobject_cast(form.get()); ssgForm->setCore(bt_); ssgForm->setConfiguration(config_.lock()); ssgForm->setColorPalette(palette_); ssgForm->resize(config_.lock()->getInstrumentSSGWindowWidth(), config_.lock()->getInstrumentSSGWindowHeight()); QObject::connect(ssgForm, &InstrumentEditorSSGForm::waveFormNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveFormNumberChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::waveFormParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveFormParameterChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::toneNoiseNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseNumberChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::toneNoiseParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseParameterChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::envelopeNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeNumberChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::envelopeParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeParameterChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::arpeggioNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioNumberChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::arpeggioParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioParameterChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::pitchNumberChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchNumberChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::pitchParameterChanged, instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchParameterChanged); QObject::connect(ssgForm, &InstrumentEditorSSGForm::jamKeyOnEvent, this, [&](JamKey key) { bt_->jamKeyOnForced(key, SoundSource::SSG); }, Qt::DirectConnection); QObject::connect(ssgForm, &InstrumentEditorSSGForm::jamKeyOffEvent, this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::SSG); }, Qt::DirectConnection); QObject::connect(ssgForm, &InstrumentEditorSSGForm::octaveChanged, this, &MainWindow::changeOctave, Qt::DirectConnection); QObject::connect(ssgForm, &InstrumentEditorSSGForm::modified, this, &MainWindow::setModifiedTrue); QObject::connect(ssgForm, &InstrumentEditorSSGForm::playStatusChanged, this, playFunc); ssgForm->installEventFilter(this); instForms_->onInstrumentSSGWaveFormNumberChanged(); instForms_->onInstrumentSSGToneNoiseNumberChanged(); instForms_->onInstrumentSSGEnvelopeNumberChanged(); instForms_->onInstrumentSSGArpeggioNumberChanged(); instForms_->onInstrumentSSGPitchNumberChanged(); break; } default: break; } } void MainWindow::on_instrumentListWidget_itemSelectionChanged() { int num = (ui->instrumentListWidget->currentRow() == -1) ? -1 : ui->instrumentListWidget->currentItem()->data(Qt::UserRole).toInt(); bt_->setCurrentInstrument(num); if (num == -1) statusInst_->setText(tr("No instrument")); else statusInst_->setText( tr("Instrument: %1").arg(QString("%1").arg(num, 2, 16, QChar('0')).toUpper())); if (bt_->findFirstFreeInstrumentNumber() == -1) { // Max size ui->actionNew_Instrument->setEnabled(false); ui->actionLoad_From_File->setEnabled(false); ui->actionImport_From_Bank_File->setEnabled(false); } else { switch (bt_->getCurrentTrackAttribute().source) { case SoundSource::DRUM: ui->actionNew_Instrument->setEnabled(false); break; default: break; } } bool isEnabled = (num != -1); ui->actionRemove_Instrument->setEnabled(isEnabled); ui->actionClone_Instrument->setEnabled(isEnabled); ui->actionDeep_Clone_Instrument->setEnabled(isEnabled); ui->actionSave_To_File->setEnabled(isEnabled); ui->actionExport_To_Bank_File->setEnabled(isEnabled); ui->actionRename_Instrument->setEnabled(isEnabled); ui->actionEdit->setEnabled(isEnabled); } void MainWindow::on_grooveCheckBox_stateChanged(int arg1) { if (arg1 == Qt::Checked) { ui->tempoSpinBox->setEnabled(false); ui->speedSpinBox->setEnabled(false); ui->grooveSpinBox->setEnabled(true); bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), false); } else { ui->tempoSpinBox->setEnabled(true); ui->speedSpinBox->setEnabled(true); ui->grooveSpinBox->setEnabled(false); bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), true); } setModifiedTrue(); } void MainWindow::on_actionExit_triggered() { close(); } void MainWindow::on_actionUndo_triggered() { undo(); } void MainWindow::on_actionRedo_triggered() { redo(); } void MainWindow::on_actionCut_triggered() { if (isEditedPattern_) ui->patternEditor->cutSelectedCells(); } void MainWindow::on_actionCopy_triggered() { if (isEditedPattern_) ui->patternEditor->copySelectedCells(); else if (isEditedOrder_) ui->orderList->copySelectedCells(); } void MainWindow::on_actionPaste_triggered() { if (isEditedPattern_) ui->patternEditor->onPastePressed(); else if (isEditedOrder_) ui->orderList->onPastePressed(); } void MainWindow::on_actionDelete_triggered() { if (isEditedPattern_) ui->patternEditor->onDeletePressed(); else if (isEditedOrder_) ui->orderList->deleteOrder(); else if (isEditedInstList_) on_actionRemove_Instrument_triggered(); } void MainWindow::updateMenuByPattern() { isEditedPattern_ = true; isEditedOrder_ = false; isEditedInstList_ = false; if (bt_->isJamMode()) { // Edit ui->actionPaste->setEnabled(false); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->actionDelete->setEnabled(false); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); } else { // Edit bool enabled = QApplication::clipboard()->text().startsWith("PATTERN_"); ui->actionPaste->setEnabled(enabled); ui->actionMix->setEnabled(enabled); ui->actionOverwrite->setEnabled(enabled); ui->actionDelete->setEnabled(true); // Pattern ui->actionInterpolate->setEnabled(isSelectedPO_); ui->actionReverse->setEnabled(isSelectedPO_); ui->actionReplace_Instrument->setEnabled( isSelectedPO_ && ui->instrumentListWidget->currentRow() != -1); ui->actionExpand->setEnabled(isSelectedPO_); ui->actionShrink->setEnabled(isSelectedPO_); ui->actionDecrease_Note->setEnabled(true); ui->actionIncrease_Note->setEnabled(true); ui->actionDecrease_Octave->setEnabled(true); ui->actionIncrease_Octave->setEnabled(true); } } void MainWindow::updateMenuByOrder() { isEditedPattern_ = false; isEditedOrder_ = true; isEditedInstList_ = false; // Edit bool enabled = QApplication::clipboard()->text().startsWith("ORDER_"); ui->actionPaste->setEnabled(enabled); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->actionDelete->setEnabled(true); // Song bool canAdd = bt_->canAddNewOrder(bt_->getCurrentSongNumber()); ui->actionInsert_Order->setEnabled(canAdd); //ui->actionRemove_Order->setEnabled(true); ui->actionDuplicate_Order->setEnabled(canAdd); //ui->actionMove_Order_Up->setEnabled(true); //ui->actionMove_Order_Down->setEnabled(true); ui->actionClone_Patterns->setEnabled(canAdd); ui->actionClone_Order->setEnabled(canAdd); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); } void MainWindow::updateMenuByInstrumentList() { isEditedPattern_ = false; isEditedOrder_ = false; isEditedInstList_ = true; // Edit ui->actionPaste->setEnabled(false); ui->actionMix->setEnabled(false); ui->actionOverwrite->setEnabled(false); ui->actionDelete->setEnabled(true); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); ui->actionDecrease_Note->setEnabled(false); ui->actionIncrease_Note->setEnabled(false); ui->actionDecrease_Octave->setEnabled(false); ui->actionIncrease_Octave->setEnabled(false); } void MainWindow::updateMenuByPatternAndOrderSelection(bool isSelected) { isSelectedPO_ = isSelected; if (bt_->isJamMode()) { // Edit ui->actionCopy->setEnabled(false); ui->actionCut->setEnabled(false); // Pattern ui->actionInterpolate->setEnabled(false); ui->actionReverse->setEnabled(false); ui->actionReplace_Instrument->setEnabled(false); ui->actionExpand->setEnabled(false); ui->actionShrink->setEnabled(false); } else { // Edit ui->actionCopy->setEnabled(isSelected); ui->actionCut->setEnabled(isEditedPattern_ ? isSelected : false); // Pattern bool enabled = (isEditedPattern_ && isEditedPattern_) ? isSelected : false; ui->actionInterpolate->setEnabled(enabled); ui->actionReverse->setEnabled(enabled); ui->actionReplace_Instrument->setEnabled( enabled && ui->instrumentListWidget->currentRow() != -1); ui->actionExpand->setEnabled(enabled); ui->actionShrink->setEnabled(enabled); } } void MainWindow::on_actionAll_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(1); else if (isEditedOrder_) ui->orderList->onSelectPressed(1); } void MainWindow::on_actionNone_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(0); else if (isEditedOrder_) ui->orderList->onSelectPressed(0); } void MainWindow::on_actionDecrease_Note_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, false); } void MainWindow::on_actionIncrease_Note_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, true); } void MainWindow::on_actionDecrease_Octave_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, false); } void MainWindow::on_actionIncrease_Octave_triggered() { if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, true); } void MainWindow::on_actionInsert_Order_triggered() { ui->orderList->insertOrderBelow(); } void MainWindow::on_actionRemove_Order_triggered() { ui->orderList->deleteOrder(); } void MainWindow::on_actionModule_Properties_triggered() { ModulePropertiesDialog dialog(bt_, config_.lock()->getMixerVolumeFM(), config_.lock()->getMixerVolumeSSG()); if (dialog.exec() == QDialog::Accepted && showUndoResetWarningDialog(tr("Do you want to change song properties?"))) { int instRow = ui->instrumentListWidget->currentRow(); bt_->stopPlaySong(); lockWidgets(false); dialog.onAccepted(); freezeViews(); if (!timer_) stream_->stop(); loadModule(); setModifiedTrue(); setWindowTitle(); ui->instrumentListWidget->setCurrentRow(instRow); if (!timer_) stream_->start(); } } void MainWindow::on_actionNew_Instrument_triggered() { addInstrument(); } void MainWindow::on_actionRemove_Instrument_triggered() { removeInstrument(ui->instrumentListWidget->currentRow()); } void MainWindow::on_actionClone_Instrument_triggered() { cloneInstrument(); } void MainWindow::on_actionDeep_Clone_Instrument_triggered() { deepCloneInstrument(); } void MainWindow::on_actionEdit_triggered() { editInstrument(); } void MainWindow::on_actionPlay_triggered() { startPlaySong(); } void MainWindow::on_actionPlay_Pattern_triggered() { startPlayPattern(); } void MainWindow::on_actionPlay_From_Start_triggered() { startPlayFromStart(); } void MainWindow::on_actionPlay_From_Cursor_triggered() { startPlayFromCurrentStep(); } void MainWindow::on_actionStop_triggered() { stopPlaySong(); } void MainWindow::on_actionEdit_Mode_triggered() { bt_->toggleJamMode(); ui->orderList->changeEditable(); ui->patternEditor->changeEditable(); if (isEditedOrder_) updateMenuByOrder(); else if (isEditedPattern_) updateMenuByPattern(); updateMenuByPatternAndOrderSelection(isSelectedPO_); if (bt_->isJamMode()) statusDetail_->setText(tr("Change to jam mode")); else statusDetail_->setText(tr("Change to edit mode")); } void MainWindow::on_actionToggle_Track_triggered() { ui->patternEditor->onToggleTrackPressed(); } void MainWindow::on_actionSolo_Track_triggered() { ui->patternEditor->onSoloTrackPressed(); } void MainWindow::on_actionKill_Sound_triggered() { bt_->killSound(); } void MainWindow::on_actionAbout_triggered() { QMessageBox box(QMessageBox::NoIcon, tr("About"), QString("

BambooTracker v%1

").arg( QString::fromStdString(Version::ofApplicationInString())) + tr("YM2608 (OPNA) Music Tracker
" "Copyright (C) 2018-2020 Rerrah

" "
" "Libraries:
" "- C86CTL by (C) honet (BSD 3-Clause)
" "- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)
" "- MAME (MAME License)
" "- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)
" "and (C) Jean Pierre Cimalando (LGPL v2.1)
" "- RtAudio by (C) Gary P. Scavone (RtAudio License)
" "- RtMidi by (C) Gary P. Scavone (RtMidi License)
" "- SCCI by (C) gasshi (SCCI License)
" "- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)
" "- Qt (GPL v2+ or LGPL v3)
" "- VGMPlay by (C) Valley Bell (GPL v2)
" "
" "Also see changelog which lists contributors."), QMessageBox::Ok, this); box.setIconPixmap(QIcon(":/icon/app_icon").pixmap(QSize(44, 44))); box.exec(); } void MainWindow::on_actionFollow_Mode_triggered() { bt_->setFollowPlay(ui->actionFollow_Mode->isChecked()); config_.lock()->setFollowMode(ui->actionFollow_Mode->isChecked()); ui->orderList->onFollowModeChanged(); ui->patternEditor->onFollowModeChanged(); } void MainWindow::on_actionGroove_Settings_triggered() { std::vector> seqs(bt_->getGrooveCount()); std::generate(seqs.begin(), seqs.end(), [&, i = 0]() mutable { return bt_->getGroove(i++); }); GrooveSettingsDialog diag; diag.setGrooveSquences(seqs); if (diag.exec() == QDialog::Accepted) { bt_->stopPlaySong(); lockWidgets(false); bt_->setGrooves(diag.getGrooveSequences()); ui->grooveSpinBox->setMaximum(static_cast(bt_->getGrooveCount()) - 1); setModifiedTrue(); } } void MainWindow::on_actionConfiguration_triggered() { ConfigurationDialog diag(config_.lock(), palette_, stream_->getCurrentBackend(), stream_->getAvailableBackends()); QObject::connect(&diag, &ConfigurationDialog::applyPressed, this, &MainWindow::changeConfiguration); if (diag.exec() == QDialog::Accepted) { bt_->stopPlaySong(); changeConfiguration(); ConfigurationHandler::saveConfiguration(config_.lock()); ColorPaletteHandler::savePalette(palette_); lockWidgets(false); } } void MainWindow::on_actionExpand_triggered() { ui->patternEditor->onExpandPressed(); } void MainWindow::on_actionShrink_triggered() { ui->patternEditor->onShrinkPressed(); } void MainWindow::on_actionDuplicate_Order_triggered() { ui->orderList->onDuplicatePressed(); } void MainWindow::on_actionMove_Order_Up_triggered() { ui->orderList->onMoveOrderPressed(true); } void MainWindow::on_actionMove_Order_Down_triggered() { ui->orderList->onMoveOrderPressed(false); } void MainWindow::on_actionClone_Patterns_triggered() { ui->orderList->onClonePatternsPressed(); } void MainWindow::on_actionClone_Order_triggered() { ui->orderList->onCloneOrderPressed(); } void MainWindow::on_actionNew_triggered() { if (isWindowModified()) { auto modTitleStd = bt_->getModuleTitle(); QString modTitle = QString::fromUtf8(modTitleStd.c_str(), static_cast(modTitleStd.length())); if (modTitle.isEmpty()) modTitle = tr("Untitled"); QMessageBox dialog(QMessageBox::Warning, "BambooTracker", tr("Save changes to %1?").arg(modTitle), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (dialog.exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } bt_->stopPlaySong(); lockWidgets(false); freezeViews(); if (!timer_) stream_->stop(); bt_->makeNewModule(); loadModule(); setInitialSelectedInstrument(); isModifiedForNotCommand_ = false; setWindowModified(false); if (!timer_) stream_->start(); } void MainWindow::on_actionComments_triggered() { auto comment = bt_->getModuleComment(); CommentEditDialog diag(QString::fromUtf8(comment.c_str(), static_cast(comment.length()))); if (diag.exec() == QDialog::Accepted) { bt_->setModuleComment(diag.getComment().toUtf8().toStdString()); setModifiedTrue(); } } bool MainWindow::on_actionSave_triggered() { auto path = QString::fromStdString(bt_->getModulePath()); if (!path.isEmpty() && QFileInfo::exists(path) && QFileInfo(path).isFile()) { if (!backupModule(path)) return false; try { BinaryContainer container; bt_->saveModule(container); QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::Mod); fp.write(container.getPointer(), container.size()); fp.close(); isModifiedForNotCommand_ = false; isSavedModBefore_ = true; setWindowModified(false); setWindowTitle(); return true; } catch (FileIOError& e) { showFileIOErrorDialog(e); return false; } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::Mod), "\n" + QString(e.what())); return false; } } else { return on_actionSave_As_triggered(); } } bool MainWindow::on_actionSave_As_triggered() { QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getSaveFileName( this, tr("Save module"), QString("%1/%2.btm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("BambooTracker module (*.btm)")); if (file.isNull()) return false; if (!file.endsWith(".btm")) file += ".btm"; // For linux if (QFile::exists(file)) { // Backup if the module already exists if (!backupModule(file)) return false; } bt_->setModulePath(file.toStdString()); try { BinaryContainer container; bt_->saveModule(container); QFile fp(file); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::Mod); fp.write(container.getPointer(), container.size()); fp.close(); isModifiedForNotCommand_ = false; isSavedModBefore_ = true; setWindowModified(false); setWindowTitle(); config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString()); changeFileHistory(file); return true; } catch (FileIOError& e) { showFileIOErrorDialog(e); return false; } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::Mod), "\n" + QString(e.what())); return false; } } void MainWindow::on_actionOpen_triggered() { if (isWindowModified()) { auto modTitleStd = bt_->getModuleTitle(); QString modTitle = QString::fromUtf8(modTitleStd.c_str(), static_cast(modTitleStd.length())); if (modTitle.isEmpty()) modTitle = tr("Untitled"); QMessageBox dialog(QMessageBox::Warning, "BambooTracker", tr("Save changes to %1?").arg(modTitle), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch (dialog.exec()) { case QMessageBox::Yes: if (!on_actionSave_triggered()) return; break; case QMessageBox::No: break; case QMessageBox::Cancel: return; default: break; } } QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString file = QFileDialog::getOpenFileName(this, tr("Open module"), (dir.isEmpty() ? "./" : dir), tr("BambooTracker module (*.btm)")); if (file.isNull()) return; bt_->stopPlaySong(); lockWidgets(false); openModule(file); } void MainWindow::on_actionLoad_From_File_triggered() { loadInstrument(); } void MainWindow::on_actionSave_To_File_triggered() { saveInstrument(); } void MainWindow::on_actionImport_From_Bank_File_triggered() { importInstrumentsFromBank(); } void MainWindow::on_actionInterpolate_triggered() { ui->patternEditor->onInterpolatePressed(); } void MainWindow::on_actionReverse_triggered() { ui->patternEditor->onReversePressed(); } void MainWindow::on_actionReplace_Instrument_triggered() { ui->patternEditor->onReplaceInstrumentPressed(); } void MainWindow::on_actionRow_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(2); else if (isEditedOrder_) ui->orderList->onSelectPressed(2); } void MainWindow::on_actionColumn_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(3); else if (isEditedOrder_) ui->orderList->onSelectPressed(3); } void MainWindow::on_actionPattern_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(4); else if (isEditedOrder_) ui->orderList->onSelectPressed(4); } void MainWindow::on_actionOrder_triggered() { if (isEditedPattern_) ui->patternEditor->onSelectPressed(5); else if (isEditedOrder_) ui->orderList->onSelectPressed(5); } void MainWindow::on_actionRemove_Unused_Instruments_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all unused instruments?"))) { bt_->stopPlaySong(); lockWidgets(false); auto list = ui->instrumentListWidget; for (auto& n : bt_->getUnusedInstrumentIndices()) { for (int i = 0; i < list->count(); ++i) { if (list->item(i)->data(Qt::UserRole).toInt() == n) { removeInstrument(i); } } } bt_->clearUnusedInstrumentProperties(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } void MainWindow::on_actionRemove_Unused_Patterns_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all unused patterns?"))) { bt_->stopPlaySong(); lockWidgets(false); bt_->clearUnusedPatterns(); bt_->clearCommandHistory(); comStack_->clear(); setModifiedTrue(); } } void MainWindow::on_actionWAV_triggered() { WaveExportSettingsDialog diag; if (diag.exec() != QDialog::Accepted) return; QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to WAV"), QString("%1/%2.wav").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("WAV signed 16-bit PCM (*.wav)")); if (path.isNull()) return; if (!path.endsWith(".wav")) path += ".wav"; // For linux QProgressDialog progress( tr("Export to WAV"), tr("Cancel"), 0, static_cast(bt_->getAllStepCount(bt_->getCurrentSongNumber(), diag.getLoopCount())) + 3 ); progress.setValue(0); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); progress.show(); bt_->stopPlaySong(); lockWidgets(false); stream_->stop(); try { BinaryContainer container; auto bar = [&progress]() -> bool { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; bool res = bt_->exportToWav(container, diag.getSampleRate(), diag.getLoopCount(), bar); if (res) { QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::WAV); fp.write(container.getPointer(), container.size()); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::WAV), "\n" + QString(e.what())); } stream_->start(); } void MainWindow::on_actionVGM_triggered() { VgmExportSettingsDialog diag; if (diag.exec() != QDialog::Accepted) return; GD3Tag tag = diag.getGD3Tag(); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to VGM"), QString("%1/%2.vgm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("VGM file (*.vgm)")); if (path.isNull()) return; if (!path.endsWith(".vgm")) path += ".vgm"; // For linux QProgressDialog progress( tr("Export to VGM"), tr("Cancel"), 0, static_cast(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3 ); progress.setValue(0); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); progress.show(); bt_->stopPlaySong(); lockWidgets(false); stream_->stop(); try { BinaryContainer container; auto bar = [&progress]() -> bool { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; bool res = bt_->exportToVgm(container, diag.getExportTarget(), diag.enabledGD3(), tag, bar); if (res) { QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::VGM); fp.write(container.getPointer(), container.size()); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::VGM), "\n" + QString(e.what())); } stream_->start(); } void MainWindow::on_actionS98_triggered() { S98ExportSettingsDialog diag; if (diag.exec() != QDialog::Accepted) return; S98Tag tag = diag.getS98Tag(); QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory()); QString path = QFileDialog::getSaveFileName( this, tr("Export to S98"), QString("%1/%2.s98").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()), tr("S98 file (*.s98)")); if (path.isNull()) return; if (!path.endsWith(".s98")) path += ".s98"; // For linux QProgressDialog progress( tr("Export to S98"), tr("Cancel"), 0, static_cast(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3 ); progress.setValue(0); progress.setWindowFlags(progress.windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); progress.show(); bt_->stopPlaySong(); lockWidgets(false); stream_->stop(); try { BinaryContainer container; auto bar = [&progress]() -> bool { QApplication::processEvents(); progress.setValue(progress.value() + 1); return progress.wasCanceled(); }; bool res = bt_->exportToS98(container, diag.getExportTarget(), diag.enabledTag(), tag, diag.getResolution(), bar); if (res) { QFile fp(path); if (!fp.open(QIODevice::WriteOnly)) throw FileOutputError(FileIO::FileType::S98); fp.write(container.getPointer(), container.size()); fp.close(); bar(); config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString()); } } catch (FileIOError& e) { showFileIOErrorDialog(e); } catch (std::exception& e) { showFileIOErrorDialog(FileOutputError(FileIO::FileType::S98), "\n" + QString(e.what())); } stream_->start(); } void MainWindow::on_actionMix_triggered() { if (isEditedPattern_) ui->patternEditor->onPasteMixPressed(); } void MainWindow::on_actionOverwrite_triggered() { if (isEditedPattern_) ui->patternEditor->onPasteOverwritePressed(); } void MainWindow::onNewTickSignaledRealChip() { onNewTickSignaled(bt_->streamCountUp()); } void MainWindow::onNewTickSignaled(int state) { if (!state) { // New step int order = bt_->getPlayingOrderNumber(); if (order > -1) { // Playing if (isVisible() && !isMinimized()) { ui->orderList->updatePositionByOrderUpdate(firstViewUpdateRequest_); ui->patternEditor->updatePositionByStepUpdate(firstViewUpdateRequest_); firstViewUpdateRequest_ = false; } int width, base; if (config_.lock()->getShowRowNumberInHex()) { width = 2; base = 16; } else { width = 3; base = 10; } statusPlayPos_->setText( QString("%1/%2") .arg(order, width, base, QChar('0')) .arg(bt_->getPlayingStepNumber(), width, base, QChar('0')).toUpper()); } } else if (state == -1) { if (hasLockedWigets_) lockWidgets(false); } // Update BPM status if (bt_->getStreamGrooveEnabled()) { statusBpm_->setText(tr("Groove")); } else { // BPM = tempo * 6 / speed * 4 / 1st highlight double bpm = 24.0 * bt_->getStreamTempo() / bt_->getStreamSpeed() / highlight1_->value(); statusBpm_->setText(QString::number(bpm, 'f', 2) + QString(" BPM")); } } void MainWindow::on_actionClear_triggered() { fileHistory_->clearHistory(); for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i) ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i)); } void MainWindow::on_keyRepeatCheckBox_stateChanged(int arg1) { config_.lock()->setKeyRepetition(arg1 == Qt::Checked); } void MainWindow::updateVisuals() { int16_t wave[2 * OPNAController::OUTPUT_HISTORY_SIZE]; bt_->getOutputHistory(wave); ui->waveVisual->setStereoSamples(wave, OPNAController::OUTPUT_HISTORY_SIZE); } void MainWindow::on_action_Effect_List_triggered() { if (effListDiag_->isVisible()) effListDiag_->activateWindow(); else effListDiag_->show(); } void MainWindow::on_actionShortcuts_triggered() { if (shortcutsDiag_->isVisible()) shortcutsDiag_->activateWindow(); else shortcutsDiag_->show(); } void MainWindow::on_actionExport_To_Bank_File_triggered() { exportInstrumentsToBank(); } void MainWindow::on_actionE_xpand_Effect_Column_triggered() { ui->patternEditor->onExpandEffectColumn(); } void MainWindow::on_actionS_hrink_Effect_Column_triggered() { ui->patternEditor->onShrinkEffectColumn(); } void MainWindow::on_actionRemove_Duplicate_Instruments_triggered() { if (showUndoResetWarningDialog(tr("Do you want to remove all duplicate instruments?"))) { bt_->stopPlaySong(); lockWidgets(false); std::vector> duplicates = bt_->checkDuplicateInstruments(); auto list = ui->instrumentListWidget; for (auto& group : duplicates) { for (size_t i = 1; i < group.size(); ++i) { for (int j = 0; j < list->count(); ++j) { if (list->item(j)->data(Qt::UserRole).toInt() == group[i]) removeInstrument(j); } } } bt_->replaceDuplicateInstrumentsInPatterns(duplicates); bt_->clearUnusedInstrumentProperties(); bt_->clearCommandHistory(); comStack_->clear(); ui->patternEditor->onDuplicateInstrumentsRemoved(); setModifiedTrue(); } } void MainWindow::on_actionRename_Instrument_triggered() { renameInstrument(); } BambooTracker-0.3.5/BambooTracker/gui/mainwindow.hpp000066400000000000000000000201711362177441300224270ustar00rootroot00000000000000#ifndef MAINWINDOW_HPP #define MAINWINDOW_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "configuration.hpp" #include "bamboo_tracker.hpp" #include "audio_stream.hpp" #include "gui/instrument_editor/instrument_form_manager.hpp" #include "gui/color_palette.hpp" #include "timer.hpp" #include "gui/file_history.hpp" #include "gui/effect_list_dialog.hpp" #include "gui/keyboard_shortcut_list_dialog.hpp" #include "file_io_error.hpp" class AbstractBank; namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(std::weak_ptr config, QString filePath, QWidget *parent = nullptr); ~MainWindow() override; protected: bool eventFilter(QObject *watched, QEvent *event) override; void showEvent(QShowEvent* event) override; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; void resizeEvent(QResizeEvent* event) override; void moveEvent(QMoveEvent* event) override; void closeEvent(QCloseEvent* event) override; // Midi private: static void midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData); private slots: void midiKeyEvent(uchar status, uchar key, uchar velocity); void midiProgramEvent(uchar status, uchar program); private: std::unique_ptr ui; std::weak_ptr config_; std::shared_ptr palette_; std::shared_ptr bt_; std::shared_ptr stream_; std::unique_ptr timer_; std::unique_ptr visualTimer_; std::shared_ptr comStack_; std::shared_ptr fileHistory_; std::unique_ptr scciDll_, c86ctlDll_; // Instrument list std::shared_ptr instForms_; void addInstrument(); void removeInstrument(int row); void editInstrument(); int findRowFromInstrumentList(int instNum); void renameInstrument(); void cloneInstrument(); void deepCloneInstrument(); void loadInstrument(); void funcLoadInstrument(QString file); void saveInstrument(); void importInstrumentsFromBank(); void funcImportInstrumentsFromBank(QString file); void exportInstrumentsToBank(); // Undo-Redo void undo(); void redo(); bool isModifiedForNotCommand_; // Load data void loadModule(); void openModule(QString file); void loadSong(); // Play song void startPlaySong(); void startPlayFromStart(); void startPlayPattern(); void startPlayFromCurrentStep(); void stopPlaySong(); bool hasLockedWigets_; void lockWidgets(bool isLock); // Octave change void changeOctave(bool upFlag); // Configuration change void changeConfiguration(); void setRealChipInterface(RealChipInterface intf); void setMidiConfiguration(); void updateFonts(); // History change void changeFileHistory(QString file); // Backup bool backupModule(QString srcFile); void setWindowTitle(); void setModifiedTrue(); void setInitialSelectedInstrument(); QString getModuleFileBaseName() const; int getSelectedFileFilter(QString& file, QStringList& filters) const; void showFileIOErrorDialog(const FileIOError& e, const QString sub = ""); bool isEditedPattern_, isEditedOrder_, isEditedInstList_; bool isSelectedPO_; bool hasShownOnce_; bool isSavedModBefore_; bool firstViewUpdateRequest_; // Sub tool bar QSpinBox* octave_; QSpinBox *highlight1_, *highlight2_; // Status bar QLabel* statusDetail_; QLabel* statusStyle_; QLabel* statusInst_; QLabel* statusOctave_; QLabel* statusIntr_; QLabel* statusMixer_; QLabel* statusBpm_; QLabel* statusPlayPos_; // Dialogs std::unique_ptr effListDiag_; std::unique_ptr shortcutsDiag_; // Meta methods int tickEventMethod_; int midiKeyEventMethod_; int midiProgramEventMethod_; void updateInstrumentListColors(); void freezeViews(); inline bool showUndoResetWarningDialog(QString text) { return (QMessageBox::warning(this, tr("Warning"), tr("%1 If you execute this command, the command history is reset.").arg(text), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes); } inline void showStreamFailedDialog() { QMessageBox::critical(this, tr("Error"), tr("Could not open the audio stream. Please change the sound settings in Configuration."), QMessageBox::Ok, QMessageBox::Ok); } private slots: void on_instrumentListWidget_customContextMenuRequested(const QPoint &pos); void on_instrumentListWidget_itemDoubleClicked(QListWidgetItem *item); void onInstrumentListWidgetItemAdded(const QModelIndex& parent, int start, int end); void on_instrumentListWidget_itemSelectionChanged(); void on_grooveCheckBox_stateChanged(int arg1); void on_actionExit_triggered(); void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionCut_triggered(); void on_actionCopy_triggered(); void on_actionPaste_triggered(); void on_actionDelete_triggered(); void updateMenuByPattern(); void updateMenuByOrder(); void updateMenuByInstrumentList(); void updateMenuByPatternAndOrderSelection(bool isSelected); void on_actionAll_triggered(); void on_actionNone_triggered(); void on_actionDecrease_Note_triggered(); void on_actionIncrease_Note_triggered(); void on_actionDecrease_Octave_triggered(); void on_actionIncrease_Octave_triggered(); void on_actionInsert_Order_triggered(); void on_actionRemove_Order_triggered(); void on_actionModule_Properties_triggered(); void on_actionNew_Instrument_triggered(); void on_actionRemove_Instrument_triggered(); void on_actionClone_Instrument_triggered(); void on_actionDeep_Clone_Instrument_triggered(); void on_actionEdit_triggered(); void on_actionPlay_triggered(); void on_actionPlay_Pattern_triggered(); void on_actionPlay_From_Start_triggered(); void on_actionPlay_From_Cursor_triggered(); void on_actionStop_triggered(); void on_actionEdit_Mode_triggered(); void on_actionToggle_Track_triggered(); void on_actionSolo_Track_triggered(); void on_actionKill_Sound_triggered(); void on_actionAbout_triggered(); void on_actionFollow_Mode_triggered(); void on_actionGroove_Settings_triggered(); void on_actionConfiguration_triggered(); void on_actionExpand_triggered(); void on_actionShrink_triggered(); void on_actionDuplicate_Order_triggered(); void on_actionMove_Order_Up_triggered(); void on_actionMove_Order_Down_triggered(); void on_actionClone_Patterns_triggered(); void on_actionClone_Order_triggered(); void on_actionNew_triggered(); void on_actionComments_triggered(); bool on_actionSave_triggered(); bool on_actionSave_As_triggered(); void on_actionOpen_triggered(); void on_actionLoad_From_File_triggered(); void on_actionSave_To_File_triggered(); void on_actionImport_From_Bank_File_triggered(); void on_actionInterpolate_triggered(); void on_actionReverse_triggered(); void on_actionReplace_Instrument_triggered(); void on_actionRow_triggered(); void on_actionColumn_triggered(); void on_actionPattern_triggered(); void on_actionOrder_triggered(); void on_actionRemove_Unused_Instruments_triggered(); void on_actionRemove_Unused_Patterns_triggered(); void on_actionWAV_triggered(); void on_actionVGM_triggered(); void on_actionS98_triggered(); void on_actionMix_triggered(); void on_actionOverwrite_triggered(); void onNewTickSignaledRealChip(); void onNewTickSignaled(int state); void on_actionClear_triggered(); void on_keyRepeatCheckBox_stateChanged(int arg1); void updateVisuals(); void on_action_Effect_List_triggered(); void on_actionShortcuts_triggered(); void on_actionExport_To_Bank_File_triggered(); void on_actionE_xpand_Effect_Column_triggered(); void on_actionS_hrink_Effect_Column_triggered(); void on_actionRemove_Duplicate_Instruments_triggered(); void on_actionRename_Instrument_triggered(); }; #endif // MAINWINDOW_HPP BambooTracker-0.3.5/BambooTracker/gui/mainwindow.ui000066400000000000000000001310151362177441300222550ustar00rootroot00000000000000 MainWindow 0 0 900 700 true BambooTracker 0 0 0 0 0 Qt::Vertical false 9 9 9 9 9 0 0 Order List 3 3 3 3 0 0 200 0 3 QLayout::SetDefaultConstraint 0 0 Song Settings 6 6 6 6 6 0 0 Tempo Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 32 255 150 0 0 Speed Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 1 31 6 0 0 Pattern size Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false 1 256 64 0 0 Groove false false # 127 0 0 Edit settings 6 6 3 6 6 0 0 Step Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 0 false 0 256 0 0 Key repetition true 3 QLayout::SetDefaultConstraint 0 0 Module Settings 6 6 6 6 0 0 Title 0 0 Author 0 0 Copyright 0 0 180 45 Songs 6 6 6 6 0 0 0 0 Instruments 0 3 3 3 3 0 0 QFrame::Panel 16 16 true QListView::Adjust 0 0 0 0 900 22 &File &Export &Recent Files &Edit &Select Paste Specia&l Patter&n &Transpose Son&g &Module Clean&up &Instrument &Tracker &Help Main toolbar 16 16 TopToolBarArea false Secondary toolbar TopToolBarArea false true :/icon/new:/icon/new &New... Ctrl+N true :/icon/open:/icon/open &Open... Ctrl+O true :/icon/save:/icon/save &Save Ctrl+S true Save &As... E&xit false :/icon/undo:/icon/undo &Undo Ctrl+Z false :/icon/redo:/icon/redo &Redo Ctrl+Y :/icon/cut:/icon/cut Cu&t Ctrl+X :/icon/copy:/icon/copy &Copy Ctrl+C :/icon/paste:/icon/paste &Paste Ctrl+V &Delete Del &All Ctrl+A &None Esc false E&xpand false S&hrink &Decrease Note Ctrl+F1 &Increase Note Ctrl+F2 D&ecrease Octave Ctrl+F3 I&ncrease Octave Ctrl+F4 :/icon/insert_order:/icon/insert_order &Insert Order :/icon/remove_order:/icon/remove_order &Remove Order :/icon/property:/icon/property &Module Properties... Ctrl+P :/icon/add_inst:/icon/add_inst &New Instrument false :/icon/remove_inst:/icon/remove_inst &Remove Instrument false :/icon/clone_inst:/icon/clone_inst &Clone Instrument false &Deep Clone Instrument true :/icon/load_inst:/icon/load_inst &Load From File... false :/icon/save_inst:/icon/save_inst &Save To File... false :/icon/edit_inst:/icon/edit_inst &Edit... Ctrl+I :/icon/play:/icon/play &Play :/icon/play_pattern:/icon/play_pattern Play P&attern F6 Play &From Start F5 Play From C&ursor F7 :/icon/stop:/icon/stop &Stop F8 true :/icon/record:/icon/record &Edit Mode Space To&ggle Track Alt+F9 S&olo Track Alt+F10 &Kill Sound F12 &About... true true Fo&llow Mode ScrollLock &Groove Settings... :/icon/config:/icon/config &Configuration... :/icon/duplicate_order:/icon/duplicate_order &Duplicate Order Ctrl+D :/icon/order_up:/icon/order_up Move Order &Up :/icon/order_down:/icon/order_down Move Order Do&wn &Clone Patterns Alt+D Clone &Order &Comments... false &Interpolate Ctrl+G false &Reverse Ctrl+R false R&eplace Instrument Alt+S &Row &Column &Pattern &Order Remove Unused &Instruments Remove Unused &Patterns &WAV... &VGM... &Mix Ctrl+M &Overwrite &Import From Bank File... &S98... &Clear &Effect List... Effect List F1 &Shortcuts... false E&xport To Bank File... E&xpand Effect Column Alt+L S&hrink Effect Column Alt+K Remove &Duplicate Instruments false :/icon/rename_inst:/icon/rename_inst Re&name Instrument PatternEditor QFrame
gui/pattern_editor/pattern_editor.hpp
1
OrderListEditor QFrame
gui/order_list_editor/order_list_editor.hpp
1
WaveVisual QWidget
gui/wave_visual.hpp
1
tempoSpinBox speedSpinBox patternSizeSpinBox grooveCheckBox grooveSpinBox editableStepSpinBox keyRepeatCheckBox modTitleLineEdit authorLineEdit copyrightLineEdit songComboBox instrumentListWidget
BambooTracker-0.3.5/BambooTracker/gui/module_properties_dialog.cpp000066400000000000000000000162571362177441300253400ustar00rootroot00000000000000#include "module_properties_dialog.hpp" #include "ui_module_properties_dialog.h" #include #include ModulePropertiesDialog::ModulePropertiesDialog(std::weak_ptr core, double configFmMixer, double configSsgMixer, QWidget *parent) : QDialog(parent), ui(new Ui::ModulePropertiesDialog), bt_(core), configFmMixer_(configFmMixer), configSsgMixer_(configSsgMixer) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); int tickFreq = static_cast(core.lock()->getModuleTickFrequency()); ui->customTickFreqSpinBox->setValue(tickFreq); switch (tickFreq) { case 60: ui->ntscRadioButton->setChecked(true); break; case 50: ui->palRadioButton->setChecked(true); break; default: ui->customTickFreqRadioButton->setChecked(true); break; } MixerType mixType = core.lock()->getModuleMixerType(); if (mixType == MixerType::UNSPECIFIED) { ui->mixerGroupBox->setChecked(false); } else { ui->mixerGroupBox->setChecked(true); ui->mixerTypeComboBox->setCurrentIndex(static_cast(mixType) - 1); } setCustomMixerLevels(core.lock()->getModuleCustomMixerFMLevel(), core.lock()->getModuleCustomMixerSSGLevel()); ui->songTreeWidget->setColumnCount(3); ui->songTreeWidget->setHeaderLabels({ tr("Number"), tr("Title"), tr("Song type") }); ui->songTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); int songCnt = static_cast(core.lock()->getSongCount()); for (int i = 0; i < songCnt; ++i) { auto title = core.lock()->getSongTitle(i); insertSong(i, QString::fromUtf8(title.c_str(), static_cast(title.length())), core.lock()->getSongStyle(i).type, i); } ui->insertTypeComboBox->addItem(tr("Standard"), static_cast(SongType::Standard)); ui->insertTypeComboBox->addItem(tr("FM3ch expanded"), static_cast(SongType::FM3chExpanded)); } ModulePropertiesDialog::~ModulePropertiesDialog() { delete ui; } void ModulePropertiesDialog::insertSong(int row, QString title, SongType type, int prevNum) { QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, QString::number(row)); item->setData(0, Qt::UserRole, prevNum); item->setText(1, title); switch (type) { case SongType::Standard: item->setText(2, tr("Standard")); break; case SongType::FM3chExpanded: item->setText(2, tr("FM3ch expanded")); break; } item->setData(2, Qt::UserRole, static_cast(type)); ui->songTreeWidget->insertTopLevelItem(row, item); for (int i = row + 1; i < ui->songTreeWidget->topLevelItemCount(); ++i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } checkButtonsEnabled(); } void ModulePropertiesDialog::checkButtonsEnabled() { if (ui->songTreeWidget->currentItem() != nullptr && ui->songTreeWidget->topLevelItemCount() > 1) { ui->upToolButton->setEnabled(true); ui->downToolButton->setEnabled(true); ui->removePushButton->setEnabled(true); } else { ui->upToolButton->setEnabled(false); ui->downToolButton->setEnabled(false); ui->removePushButton->setEnabled(false); } } void ModulePropertiesDialog::swapset(int aboveRow, int belowRow) { auto* tree = ui->songTreeWidget; QTreeWidgetItem* below = tree->takeTopLevelItem(belowRow); if (tree->topLevelItemCount() > 2) { QTreeWidgetItem* above = tree->takeTopLevelItem(aboveRow); tree->insertTopLevelItem(aboveRow, below); tree->insertTopLevelItem(belowRow, above); } else { tree->insertTopLevelItem(aboveRow, below); } for (int i = aboveRow; i < ui->songTreeWidget->topLevelItemCount(); ++i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } } void ModulePropertiesDialog::setCustomMixerLevels(double fm, double ssg) { fmMixer_ = fm; ssgMixer_ = ssg; ui->customMixerFMLevelLabel->setText(QString::asprintf("%+.1fdB", fmMixer_)); ui->customMixerSSGLevelLabel->setText(QString::asprintf("%+.1fdB", ssgMixer_)); } /******************************/ void ModulePropertiesDialog::on_upToolButton_clicked() { int curRow = ui->songTreeWidget->currentIndex().row(); if (!curRow) return; swapset(curRow - 1, curRow); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(curRow - 1)); } void ModulePropertiesDialog::on_downToolButton_clicked() { int curRow = ui->songTreeWidget->currentIndex().row(); if (curRow == ui->songTreeWidget->topLevelItemCount() - 1) return; swapset(curRow, curRow + 1); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(curRow + 1)); } void ModulePropertiesDialog::on_removePushButton_clicked() { int row = ui->songTreeWidget->currentIndex().row(); auto del = ui->songTreeWidget->takeTopLevelItem(row); delete del; for (int i = row; i < ui->songTreeWidget->topLevelItemCount(); ++ i) { ui->songTreeWidget->topLevelItem(i)->setText(0, QString::number(i)); } checkButtonsEnabled(); } void ModulePropertiesDialog::on_insertPushButton_clicked() { int row = ui->songTreeWidget->currentIndex().row(); if (row == -1) row = ui->songTreeWidget->topLevelItemCount(); insertSong(row, ui->insertTitleLineEdit->text(), static_cast(ui->insertTypeComboBox->currentData(Qt::UserRole).toInt())); ui->songTreeWidget->setCurrentItem(ui->songTreeWidget->topLevelItem(row)); } void ModulePropertiesDialog::on_songTreeWidget_itemSelectionChanged() { ui->editTitleLineEdit->setText(ui->songTreeWidget->currentItem()->text(1)); checkButtonsEnabled(); } void ModulePropertiesDialog::on_editTitleLineEdit_textEdited(const QString &arg1) { if (ui->songTreeWidget->currentItem() != nullptr) ui->songTreeWidget->currentItem()->setText(1, arg1); } void ModulePropertiesDialog::onAccepted() { // Set tick frequency unsigned int tickFreq; if (ui->ntscRadioButton->isChecked()) tickFreq = 60; else if (ui->palRadioButton->isChecked()) tickFreq = 50; else tickFreq = static_cast(ui->customTickFreqSpinBox->value()); bt_.lock()->setModuleTickFrequency(tickFreq); // Set mixer if (ui->mixerGroupBox->isChecked()) { auto mixType = static_cast(ui->mixerTypeComboBox->currentIndex() + 1); bt_.lock()->setModuleMixerType(mixType); if (mixType == MixerType::CUSTOM) { bt_.lock()->setModuleCustomMixerFMLevel(fmMixer_); bt_.lock()->setModuleCustomMixerSSGLevel(ssgMixer_); } } else { bt_.lock()->setModuleMixerType(MixerType::UNSPECIFIED); } auto* tree = ui->songTreeWidget; std::vector newSongNums; for (int i = 0; i < tree->topLevelItemCount(); ++i) { QTreeWidgetItem* item = tree->topLevelItem(i); if (item->data(0, Qt::UserRole).toInt() == -1) { // Add new song int n = static_cast(bt_.lock()->getSongCount()); bt_.lock()->addSong(static_cast(item->data(2, Qt::UserRole).toInt()), item->text(1).toUtf8().toStdString()); newSongNums.push_back(n); } else { // Update song meta data int n = item->data(0, Qt::UserRole).toInt(); bt_.lock()->setSongTitle(n, item->text(1).toUtf8().toStdString()); newSongNums.push_back(n); } } // Sort songs bt_.lock()->sortSongs(std::move(newSongNums)); } void ModulePropertiesDialog::on_mixerTypeComboBox_currentIndexChanged(int index) { ui->mixerCustomGroupBox->setEnabled(index == 0); } void ModulePropertiesDialog::on_customMixerSetPushButton_clicked() { setCustomMixerLevels(configFmMixer_, configSsgMixer_); } BambooTracker-0.3.5/BambooTracker/gui/module_properties_dialog.hpp000066400000000000000000000024261362177441300253360ustar00rootroot00000000000000#ifndef MODULE_PROPERTIES_DIALOG_HPP #define MODULE_PROPERTIES_DIALOG_HPP #include #include #include #include #include "bamboo_tracker.hpp" #include "misc.hpp" namespace Ui { class ModulePropertiesDialog; } class ModulePropertiesDialog : public QDialog { Q_OBJECT public: ModulePropertiesDialog(std::weak_ptr core, double configFmMixer, double configSsgMixer, QWidget *parent = nullptr); ~ModulePropertiesDialog() override; public slots: void onAccepted(); private slots: void on_upToolButton_clicked(); void on_downToolButton_clicked(); void on_removePushButton_clicked(); void on_insertPushButton_clicked(); void on_songTreeWidget_itemSelectionChanged(); void on_editTitleLineEdit_textEdited(const QString &arg1); void on_mixerTypeComboBox_currentIndexChanged(int index); void on_customMixerSetPushButton_clicked(); private: Ui::ModulePropertiesDialog *ui; std::weak_ptr bt_; double fmMixer_, ssgMixer_; double configFmMixer_, configSsgMixer_; void insertSong(int row, QString title, SongType type, int prevNum = -1); void checkButtonsEnabled(); void swapset(int aboveRow, int belowRow); void setCustomMixerLevels(double fm, double ssg); }; #endif // MODULE_PROPERTIES_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/module_properties_dialog.ui000066400000000000000000000301421362177441300251600ustar00rootroot00000000000000 ModulePropertiesDialog 0 0 417 491 Module properties Tick frequency 60Hz (NTSC) true tickFreqButtonGroup 50Hz (PAL) tickFreqButtonGroup 0 Custom tickFreqButtonGroup Hz 1 60 Mixer true true 1 Custom PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR false Custom mixer FM Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::WinPanel QFrame::Sunken +0.0dB Qt::AlignCenter SSG Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::WinPanel QFrame::Sunken +0.0dB Qt::AlignCenter Set Song control false Remove Qt::Vertical 20 40 false 0 0 Qt::DownArrow 1 Insert song Title Song type true Qt::Horizontal 40 20 0 0 Insert Untitled false 0 0 Qt::UpArrow true Untitled Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ntscRadioButton palRadioButton customTickFreqRadioButton customTickFreqSpinBox mixerGroupBox mixerTypeComboBox customMixerSetPushButton songTreeWidget upToolButton downToolButton removePushButton editTitleLineEdit insertTitleLineEdit insertTypeComboBox insertPushButton buttonBox accepted() ModulePropertiesDialog accept() 248 254 157 274 buttonBox rejected() ModulePropertiesDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/000077500000000000000000000000001362177441300232555ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_list_editor.cpp000066400000000000000000000147151362177441300275050ustar00rootroot00000000000000#include "order_list_editor.hpp" #include "ui_order_list_editor.h" OrderListEditor::OrderListEditor(QWidget *parent) : QFrame(parent), ui(new Ui::OrderListEditor), freezed_(false), songLoaded_(false), hScrollCellMove_(true) { ui->setupUi(this); installEventFilter(this); ui->panel->installEventFilter(this); ui->verticalScrollBar->installEventFilter(this); QObject::connect(ui->panel, &OrderListPanel::hScrollBarChangeRequested, ui->horizontalScrollBar, &QScrollBar::setValue); QObject::connect(ui->panel, &OrderListPanel::vScrollBarChangeRequested, this, [&](int num, int max) { if (ui->verticalScrollBar->maximum() < num) { ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } else { ui->verticalScrollBar->setValue(num); ui->verticalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &OrderListPanel::currentTrackChanged, this, [&](int num) { emit currentTrackChanged(num); }); QObject::connect(ui->panel, &OrderListPanel::currentOrderChanged, this, [&](int num) { emit currentOrderChanged(num); }); QObject::connect(ui->panel, &OrderListPanel::orderEdited, this, [&] { emit orderEdited(); }); QObject::connect(ui->panel, &OrderListPanel::selected, this, [&](bool isSelected) { emit selected(isSelected); }); QObject::connect(ui->panel, &OrderListPanel::returnPressed, this, [&] { emit returnPressed(); }); auto focusSlot = [&] { ui->panel->setFocus(); }; QObject::connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->panel, &OrderListPanel::onHScrollBarChanged); QObject::connect(ui->horizontalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); QObject::connect(ui->verticalScrollBar, &QScrollBar::valueChanged, ui->panel, &OrderListPanel::onVScrollBarChanged); QObject::connect(ui->verticalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); } OrderListEditor::~OrderListEditor() { delete ui; } void OrderListEditor::setCore(std::shared_ptr core) { bt_ = core; ui->panel->setCore(core); } void OrderListEditor::setCommandStack(std::weak_ptr stack) { ui->panel->setCommandStack(stack); } void OrderListEditor::setConfiguration(std::shared_ptr config) { ui->panel->setConfiguration(config); } void OrderListEditor::setColorPallete(std::shared_ptr palette) { ui->panel->setColorPallete(palette); } void OrderListEditor::changeEditable() { ui->panel->changeEditable(); } void OrderListEditor::updatePositionByOrderUpdate(bool isFirstUpdate) { ui->panel->updatePositionByOrderUpdate(isFirstUpdate); } void OrderListEditor::copySelectedCells() { ui->panel->copySelectedCells(); } void OrderListEditor::deleteOrder() { ui->panel->deleteOrder(); } void OrderListEditor::insertOrderBelow() { ui->panel->insertOrderBelow(); } void OrderListEditor::freeze() { freezed_ = true; ui->panel->freeze(); } void OrderListEditor::unfreeze() { freezed_ = false; ui->panel->unfreeze(); } QString OrderListEditor::getHeaderFont() const { return ui->panel->getHeaderFont(); } int OrderListEditor::getHeaderFontSize() const { return ui->panel->getHeaderFontSize(); } QString OrderListEditor::getRowsFont() const { return ui->panel->getRowsFont(); } int OrderListEditor::getRowsFontSize() const { return ui->panel->getRowsFontSize(); } void OrderListEditor::setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize) { ui->panel->setFonts(headerFont, headerSize, rowsFont, rowsSize); } void OrderListEditor::setHorizontalScrollMode(bool cellBased, bool refresh) { hScrollCellMove_ = cellBased; if (refresh) updateHorizontalSliderMaximum(); } bool OrderListEditor::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) if (freezed_) return true; // Ignore every events if (watched == this) { if (event->type() == QEvent::FocusIn) { ui->panel->setFocus(); } return false; } if (watched == ui->panel) { switch (event->type()) { case QEvent::FocusIn: ui->panel->redrawByFocusChanged(); emit focusIn(); return false; case QEvent::FocusOut: ui->panel->redrawByFocusChanged(); emit focusOut(); return false; case QEvent::HoverEnter: case QEvent::HoverLeave: ui->panel->redrawByHoverChanged(); return false; default: return false; } } if (watched == ui->verticalScrollBar) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::DragMove: case QEvent::Wheel: return (bt_->isPlaySong() && bt_->isFollowPlay()); default: return false; } } return false; } void OrderListEditor::resizeEvent(QResizeEvent* event) { Q_UNUSED(event) // For view-based scroll updateHorizontalSliderMaximum(); } /********** Slots **********/ void OrderListEditor::setCurrentTrack(int num) { ui->panel->setCurrentTrack(num); } void OrderListEditor::setCurrentOrder(int num, int max) { ui->panel->setCurrentOrder(num); ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } void OrderListEditor::onSongLoaded() { ui->horizontalScrollBar->setValue(0); ui->panel->onSongLoaded(); setMaximumWidth(ui->panel->maximumWidth() + ui->verticalScrollBar->width() + 2); int song = bt_->getCurrentSongNumber(); songLoaded_ = true; updateHorizontalSliderMaximum(); ui->verticalScrollBar->setValue(0); // Left here to set appropriate order size before initialization of order position ui->verticalScrollBar->setMaximum(static_cast(bt_->getOrderSize(song)) - 1); } void OrderListEditor::onPastePressed() { ui->panel->onPastePressed(); } void OrderListEditor::onSelectPressed(int type) { ui->panel->onSelectPressed(type); } void OrderListEditor::onDuplicatePressed() { ui->panel->onDuplicatePressed(); } void OrderListEditor::onMoveOrderPressed(bool isUp) { ui->panel->onMoveOrderPressed(isUp); } void OrderListEditor::onClonePatternsPressed() { ui->panel->onClonePatternsPressed(); } void OrderListEditor::onCloneOrderPressed() { ui->panel->onCloneOrderPressed(); } void OrderListEditor::onFollowModeChanged() { ui->panel->onFollowModeChanged(); } void OrderListEditor::onStoppedPlaySong() { ui->panel->onStoppedPlaySong(); } void OrderListEditor::updateHorizontalSliderMaximum() { if (!bt_ || !songLoaded_) return; int song = bt_->getCurrentSongNumber(); int max = hScrollCellMove_ ? static_cast(bt_->getSongStyle(song).trackAttribs.size()) - 1 : ui->panel->getScrollableCountByTrack(); ui->horizontalScrollBar->setMaximum(max); } BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_list_editor.hpp000066400000000000000000000040341362177441300275030ustar00rootroot00000000000000#ifndef ORDER_LIST_EDITOR_HPP #define ORDER_LIST_EDITOR_HPP #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/color_palette.hpp" namespace Ui { class OrderListEditor; } class OrderListEditor : public QFrame { Q_OBJECT public: explicit OrderListEditor(QWidget *parent = nullptr); ~OrderListEditor() override; void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); void updatePositionByOrderUpdate(bool isFirstUpdate); void copySelectedCells(); void deleteOrder(); void insertOrderBelow(); void freeze(); void unfreeze(); QString getHeaderFont() const; int getHeaderFontSize() const; QString getRowsFont() const; int getRowsFontSize() const; void setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize); void setHorizontalScrollMode(bool cellBased, bool refresh = true); signals: void currentTrackChanged(int num); void currentOrderChanged(int num); void orderEdited(); void focusIn(); void focusOut(); void selected(bool isSelected); void returnPressed(); public slots: void setCurrentTrack(int num); void setCurrentOrder(int num, int max); void onSongLoaded(); void onPastePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onDuplicatePressed(); void onMoveOrderPressed(bool isUp); void onClonePatternsPressed(); void onCloneOrderPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); protected: bool eventFilter(QObject *watched, QEvent *event) override; void resizeEvent(QResizeEvent* event) override; private: Ui::OrderListEditor *ui; std::shared_ptr bt_; bool freezed_; bool songLoaded_; bool hScrollCellMove_; void updateHorizontalSliderMaximum(); }; #endif // ORDER_LIST_EDITOR_HPP BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_list_editor.ui000066400000000000000000000046711362177441300273400ustar00rootroot00000000000000 OrderListEditor 0 0 400 300 0 0 Frame QFrame::Panel QFrame::Sunken 1 0 0 0 0 0 Qt::Horizontal 0 0 17 0 17 16777215 Qt::Vertical OrderListPanel QWidget
gui/order_list_editor/order_list_panel.hpp
1
BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_list_panel.cpp000066400000000000000000001266341362177441300273220ustar00rootroot00000000000000#include "order_list_panel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gui/event_guard.hpp" #include "gui/command/order/order_commands.hpp" #include "playback.hpp" #include "track.hpp" OrderListPanel::OrderListPanel(QWidget *parent) : QWidget(parent), config_(std::make_shared()), // Dummy rowFontWidth_(0), rowFontHeight_(0), rowFontAscent_(0), rowFontLeading_(0), headerFontAscent_(0), widthSpace_(0), rowNumWidthCnt_(0), rowNumWidth_(0), rowNumBase_(0), trackWidth_(0), columnsWidthFromLeftToEnd_(0), headerHeight_(0), curRowBaselineY_(0), curRowY_(0), leftTrackNum_(0), curSongNum_(0), curPos_{ 0, 0 }, hovPos_{ -1, -1 }, mousePressPos_{ -1, -1 }, mouseReleasePos_{ -1, -1 }, selLeftAbovePos_{ -1, -1 }, selRightBelowPos_{ -1, -1 }, shiftPressedPos_{ -1, -1 }, isIgnoreToSlider_(false), isIgnoreToPattern_(false), entryCnt_(0), selectAllState_(-1), viewedRowCnt_(1), viewedRowsHeight_(0), viewedRowOffset_(0), viewedCenterY_(0), viewedCenterBaseY_(0), backChanged_(false), textChanged_(false), headerChanged_(false), followModeChanged_(false), hasFocussedBefore_(false), orderDownCount_(0), freezed_(false), repaintable_(true), repaintingCnt_(0), playingRow_(-1) { // Initialize font headerFont_ = QApplication::font(); headerFont_.setPointSize(10); rowFont_ = QFont("Monospace", 10); rowFont_.setStyleHint(QFont::TypeWriter); rowFont_.setStyleStrategy(QFont::ForceIntegerMetrics); updateSizes(); setAttribute(Qt::WA_Hover); setContextMenuPolicy(Qt::CustomContextMenu); } void OrderListPanel::setCore(std::shared_ptr core) { bt_ = core; } void OrderListPanel::setCommandStack(std::weak_ptr stack) { comStack_ = stack; } void OrderListPanel::setConfiguration(std::shared_ptr config) { config_ = config; } void OrderListPanel::setColorPallete(std::shared_ptr palette) { palette_ = palette; } void OrderListPanel::resetEntryCount() { entryCnt_ = 0; } void OrderListPanel::freeze() { freezed_ = true; while (true) { if (repaintingCnt_.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); else { curPos_.row = 0; // Init return; } } } void OrderListPanel::unfreeze() { freezed_ = false; } QString OrderListPanel::getHeaderFont() const { return QFontInfo(headerFont_).family(); } int OrderListPanel::getHeaderFontSize() const { return QFontInfo(headerFont_).pointSize(); } QString OrderListPanel::getRowsFont() const { return QFontInfo(rowFont_).family(); } int OrderListPanel::getRowsFontSize() const { return QFontInfo(rowFont_).pointSize(); } void OrderListPanel::setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize) { headerFont_ = QFont(headerFont, headerSize); rowFont_ = QFont(rowsFont, rowsSize); updateSizes(); updateTracksWidthFromLeftToEnd(); setMaximumWidth(columnsWidthFromLeftToEnd_); redrawAll(); } void OrderListPanel::updateSizes() { QFontMetrics metrics(rowFont_); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) rowFontWidth_ = metrics.horizontalAdvance('0'); #else rowFontWidth_ = metrics.width('0'); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) rowFontAscent_ = metrics.capHeight(); #else rowFontAscent_ = metrics.boundingRect('X').height(); #endif rowFontLeading_ = metrics.ascent() - rowFontAscent_ + metrics.descent() / 2; rowFontHeight_ = rowFontAscent_ + rowFontLeading_; hdFontMets_ = std::make_unique(headerFont_); headerHeight_ = hdFontMets_->height() + 4; headerFontAscent_ = hdFontMets_->ascent() + 2; /* Width & height */ widthSpace_ = rowFontWidth_ / 4; trackWidth_ = rowFontWidth_ * 3 + widthSpace_ * 2; if (config_->getShowRowNumberInHex()) { rowNumWidthCnt_ = 2; rowNumBase_ = 16; } else { rowNumWidthCnt_ = 3; rowNumBase_ = 10; } rowNumWidth_ = rowFontWidth_ * rowNumWidthCnt_ + widthSpace_; initDisplay(); } void OrderListPanel::initDisplay() { completePixmap_ = std::make_unique(geometry().size()); int width = geometry().width(); // Recalculate pixmap sizes viewedRegionHeight_ = std::max((geometry().height() - headerHeight_), rowFontHeight_); int cnt = viewedRegionHeight_ / rowFontHeight_; viewedRowCnt_ = (cnt % 2) ? (cnt + 2) : (cnt + 1); viewedRowsHeight_ = viewedRowCnt_ * rowFontHeight_; viewedRowOffset_ = (viewedRowsHeight_ - viewedRegionHeight_) >> 1; viewedCenterY_ = (viewedRowsHeight_ - rowFontHeight_) >> 1; viewedCenterBaseY_ = viewedCenterY_ + rowFontAscent_ + (rowFontLeading_ >> 1); backPixmap_ = std::make_unique(width, viewedRowsHeight_); textPixmap_ = std::make_unique(width, viewedRowsHeight_); headerPixmap_ = std::make_unique(width, headerHeight_); } void OrderListPanel::drawList(const QRect &rect) { if (!freezed_ && repaintable_.load()) { repaintable_.store(false); ++repaintingCnt_; // Use module data after this line if (backChanged_ || textChanged_ || headerChanged_ || orderDownCount_ || followModeChanged_) { int maxWidth = std::min(geometry().width(), columnsWidthFromLeftToEnd_); int trackSize = static_cast(songStyle_.trackAttribs.size()); completePixmap_->fill(palette_->odrBackColor); if (orderDownCount_ && !followModeChanged_) { quickDrawRows(maxWidth, trackSize); } else { backPixmap_->fill(Qt::transparent); if (textChanged_) textPixmap_->fill(Qt::transparent); drawRows(maxWidth, trackSize); } if (headerChanged_) { // headerPixmap_->fill(Qt::transparent); drawHeaders(maxWidth, trackSize); } { QPainter mergePainter(completePixmap_.get()); QRect rowsRect(0, viewedRowOffset_, maxWidth, viewedRegionHeight_); QRect inViewRect(0, headerHeight_, maxWidth, viewedRegionHeight_); mergePainter.drawPixmap(inViewRect, *backPixmap_.get(), rowsRect); mergePainter.drawPixmap(inViewRect, *textPixmap_.get(), rowsRect); mergePainter.drawPixmap(headerPixmap_->rect(), *headerPixmap_.get()); } drawBorders(maxWidth, trackSize); if (!hasFocus()) drawShadow(); backChanged_ = false; textChanged_ = false; headerChanged_ = false; followModeChanged_ = false; orderDownCount_ = 0; } --repaintingCnt_; // Used module data until this line repaintable_.store(true); } QPainter completePainter(this); completePainter.drawPixmap(rect, *completePixmap_.get()); } void OrderListPanel::drawRows(int maxWidth, int trackSize) { QPainter textPainter(textPixmap_.get()); QPainter backPainter(backPixmap_.get()); textPainter.setFont(rowFont_); std::vector orderRowData_; int x, trackNum; int textOffset = trackWidth_ / 2 - rowFontWidth_; /* Current row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, hasFocus() ? palette_->odrCurEditRowColor : palette_->odrCurRowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg( curPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, curPos_.row); textPainter.setPen(palette_->odrCurTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (trackNum == curPos_.track) // Paint current cell backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrCurCellColor); if (((hovPos_.row == curPos_.row || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == curPos_.row)) // Paint hover backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, curPos_.row)) // Paint selected backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, viewedCenterBaseY_, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; ++trackNum; } viewedCenterPos_.row = curPos_.row; int rowNum; int rowY, baseY, endY; int playOdrNum = bt_->getPlayingOrderNumber(); /* Previous rows */ viewedFirstPos_.row = curPos_.row; endY = std::max(0, viewedCenterY_ - rowFontHeight_ * curPos_.row); for (rowY = viewedCenterY_ - rowFontHeight_, baseY = viewedCenterBaseY_ - rowFontHeight_, rowNum = curPos_.row - 1; rowY >= endY; rowY -= rowFontHeight_, baseY -= rowFontHeight_, --rowNum) { QColor rowColor; if (!config_->getFollowMode() && rowNum == playOdrNum) { rowColor = palette_->odrPlayRowColor; } else { rowColor = palette_->odrDefRowColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, rowFontHeight_, rowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( rowNum, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, rowNum); textPainter.setPen(palette_->odrDefTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (((hovPos_.row == rowNum || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == rowNum)) // Paint hover backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, rowNum)) // Paint selected backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; ++trackNum; } viewedFirstPos_.row = rowNum; } /* Next rows */ viewedLastPos_.row = curPos_.row; endY = std::min(viewedRowsHeight_ - viewedRowOffset_, viewedCenterY_ + rowFontHeight_ * (static_cast(bt_->getOrderSize(curSongNum_)) - curPos_.row - 1)); for (rowY = viewedCenterY_ + rowFontHeight_, baseY = viewedCenterBaseY_ + rowFontHeight_, rowNum = curPos_.row + 1; rowY <= endY; rowY += rowFontHeight_, baseY += rowFontHeight_, ++rowNum) { QColor rowColor; if (!config_->getFollowMode() && rowNum == playOdrNum) rowColor = palette_->odrPlayRowColor; else rowColor = palette_->odrDefRowColor; // Fill row backPainter.fillRect(0, rowY, maxWidth, rowFontHeight_, rowColor); if (textChanged_) { // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( rowNum, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); } // Order data orderRowData_ = bt_->getOrderData(curSongNum_, rowNum); textPainter.setPen(palette_->odrDefTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (((hovPos_.row == rowNum || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == rowNum)) // Paint hover backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, rowNum)) // Paint selected backPainter.fillRect(x, rowY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); if (textChanged_) { textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); } x += trackWidth_; ++trackNum; } viewedLastPos_.row = rowNum; } } void OrderListPanel::quickDrawRows(int maxWidth, int trackSize) { int halfRowsCnt = viewedRowCnt_ >> 1; int shift = rowFontHeight_ * orderDownCount_; /* Move up by 1 step */ QRect srcRect(0, 0, maxWidth, viewedRowsHeight_); textPixmap_->scroll(0, -shift, srcRect); backPixmap_->scroll(0, -shift, srcRect); { int fpos = viewedCenterPos_.row + orderDownCount_ - halfRowsCnt; if (fpos >= 0) viewedFirstPos_.row = fpos; } QPainter textPainter(textPixmap_.get()); QPainter backPainter(backPixmap_.get()); textPainter.setFont(rowFont_); std::vector orderRowData_; int x, trackNum; int textOffset = trackWidth_ / 2 - rowFontWidth_; /* Clear previous cursor row, current cursor row and last rows text */ int prevY = viewedCenterY_ - shift; int lastY = viewedRowsHeight_ - shift; textPainter.setCompositionMode(QPainter::CompositionMode_Source); textPainter.fillRect(0, prevY, maxWidth, rowFontHeight_, Qt::transparent); textPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, Qt::transparent); textPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); textPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); /* Redraw previous cursor row */ { int baseY = viewedCenterBaseY_ - shift; // Fill row backPainter.fillRect(0, prevY, maxWidth, rowFontHeight_, palette_->odrDefRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( viewedCenterPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, viewedCenterPos_.row); textPainter.setPen(palette_->odrDefTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (((hovPos_.row == viewedCenterPos_.row || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == viewedCenterPos_.row)) // Paint hover backPainter.fillRect(x, prevY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, viewedCenterPos_.row)) // Paint selected backPainter.fillRect(x, prevY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; ++trackNum; } } /* Redraw current cursor row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, rowFontHeight_, hasFocus() ? palette_->odrCurEditRowColor : palette_->odrCurRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg( curPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, curPos_.row); textPainter.setPen(palette_->odrCurTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (trackNum == curPos_.track) // Paint current cell backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrCurCellColor); if (((hovPos_.row == curPos_.row || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == curPos_.row)) // Paint hover backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, curPos_.row)) // Paint selected backPainter.fillRect(x, viewedCenterY_, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, viewedCenterBaseY_, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; ++trackNum; } viewedCenterPos_ = curPos_; /* Draw new rows at last if necessary */ { int bpos = viewedCenterPos_.row + halfRowsCnt; int last = static_cast(bt_->getOrderSize(curSongNum_)) - 1; bool needClear; if (bpos < last) { needClear = false; bpos = std::exchange(viewedLastPos_.row, bpos); } else { needClear = true; bpos = std::exchange(viewedLastPos_.row, last); } int baseY = lastY + (viewedCenterBaseY_ - viewedCenterY_); while (true) { if (bpos == viewedLastPos_.row) { if (needClear) { // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); } break; } ++bpos; // Fill row backPainter.fillRect(0, lastY, maxWidth, rowFontHeight_, palette_->odrDefRowColor); // Row number textPainter.setPen(palette_->odrRowNumColor); textPainter.drawText(1, baseY, QString("%1").arg( viewedLastPos_.row, rowNumWidthCnt_, rowNumBase_, QChar('0') ).toUpper()); // Order data orderRowData_ = bt_->getOrderData(curSongNum_, viewedLastPos_.row); textPainter.setPen(palette_->odrDefTextColor); for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { if (((hovPos_.row == viewedLastPos_.row || hovPos_.row == -2) && hovPos_.track == trackNum) || (hovPos_.track == -2 && hovPos_.row == viewedLastPos_.row)) // Paint hover backPainter.fillRect(x, lastY, trackWidth_, rowFontHeight_, palette_->odrHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.row >= 0) && isSelectedCell(trackNum, viewedLastPos_.row)) // Paint selected backPainter.fillRect(x, lastY, trackWidth_, rowFontHeight_, palette_->odrSelCellColor); textPainter.drawText( x + textOffset, baseY, QString("%1") .arg(orderRowData_.at(static_cast(trackNum)).patten, 2, 16, QChar('0')).toUpper() ); x += trackWidth_; ++trackNum; } baseY += rowFontHeight_; lastY += rowFontHeight_; } } } void OrderListPanel::drawHeaders(int maxWidth, int trackSize) { QPainter painter(headerPixmap_.get()); painter.setFont(headerFont_); painter.fillRect(0, 0, geometry().width(), headerHeight_, palette_->odrHeaderRowColor); painter.setPen(palette_->odrHeaderTextColor); int x, trackNum; for (x = rowNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { QString str; auto& attrib = songStyle_.trackAttribs[static_cast(trackNum)]; switch (attrib.source) { case SoundSource::FM: switch (songStyle_.type) { case SongType::Standard: str = "FM" + QString::number(attrib.channelInSource + 1); break; case SongType::FM3chExpanded: switch (attrib.channelInSource) { case 2: str = "OP1"; break; case 6: str = "OP2"; break; case 7: str = "OP3"; break; case 8: str = "OP4"; break; default: str = "FM" + QString::number(attrib.channelInSource + 1); break; } break; } break; case SoundSource::SSG: str = "SG" + QString::number(attrib.channelInSource + 1); break; case SoundSource::DRUM: switch (attrib.channelInSource) { case 0: str = "BD"; break; case 1: str = "SD"; break; case 2: str = "TOP"; break; case 3: str = "HH"; break; case 4: str = "TOM"; break; case 5: str = "RIM"; break; } break; } #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) int rw = trackWidth_ - hdFontMets_->horizontalAdvance(str); #else int rw = trackWidth_ - hdFontMets_->width(str); #endif rw = (rw < 0) ? 0 : (rw / 2); painter.drawText(x + rw, headerFontAscent_, str); x += trackWidth_; ++trackNum; } } void OrderListPanel::drawBorders(int maxWidth, int trackSize) { QPainter painter(completePixmap_.get()); painter.drawLine(0, headerHeight_, geometry().width(), headerHeight_); painter.drawLine(rowNumWidth_, 0, rowNumWidth_, geometry().height()); int x, trackNum; for (x = rowNumWidth_ + trackWidth_, trackNum = leftTrackNum_; x <= maxWidth && trackNum < trackSize; ) { painter.drawLine(x, 0, x, geometry().height()); x += trackWidth_; ++trackNum; } } void OrderListPanel::drawShadow() { QPainter painter(completePixmap_.get()); painter.fillRect(0, 0, geometry().width(), geometry().height(), QColor::fromRgb(0, 0, 0, 47)); } void OrderListPanel::moveCursorToRight(int n) { int oldLeftTrack = leftTrackNum_; int prevTrack = curPos_.track; int tmp = curPos_.track + n; if (n > 0) { while (true) { int sub = tmp - static_cast(songStyle_.trackAttribs.size()); if (sub < 0) { curPos_.track = tmp; break; } else { if (config_->getWarpCursor()) { tmp = sub; } else { curPos_.track = static_cast(songStyle_.trackAttribs.size()) - 1; break; } } } } else { while (true) { int add = tmp + static_cast(songStyle_.trackAttribs.size()); if (tmp < 0) { if (config_->getWarpCursor()) { tmp = add; } else { curPos_.track = 0; break; } } else { curPos_.track = tmp; break; } } } if (prevTrack < curPos_.track) { while (calculateColumnsWidthWithRowNum(leftTrackNum_, curPos_.track) > geometry().width()) ++leftTrackNum_; } else { if (curPos_.track < leftTrackNum_) leftTrackNum_ = curPos_.track; } updateTracksWidthFromLeftToEnd(); entryCnt_ = 0; if (!isIgnoreToSlider_) { // Send to slider if (config_->getMoveCursorByHorizontalScroll()) { emit hScrollBarChangeRequested(curPos_.track); } else { emit hScrollBarChangeRequested(leftTrackNum_); } } if (!isIgnoreToPattern_) emit currentTrackChanged(curPos_.track); // Send to pattern editor // Request fore-background repaint if leftmost track is changed else request only background repaint if (leftTrackNum_ != oldLeftTrack) { headerChanged_ = true; textChanged_ = true; } backChanged_ = true; repaint(); } void OrderListPanel::moveViewToRight(int n) { leftTrackNum_ += n; updateTracksWidthFromLeftToEnd(); // Move cursor and repaint all headerChanged_ = true; textChanged_ = true; moveCursorToRight(n); } void OrderListPanel::moveCursorToDown(int n) { int tmp = curPos_.row + n; int endRow = static_cast(bt_->getOrderSize(curSongNum_)); if (n > 0) { while (true) { int sub = tmp - endRow; if (sub < 0) { curPos_.row = tmp; break; } else { tmp = sub; } } } else { while (true) { int add = tmp + endRow; if (tmp < 0) { tmp = add; } else { curPos_.row = tmp; break; } } } entryCnt_ = 0; if (!isIgnoreToSlider_) // Send to slider emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); if (!isIgnoreToPattern_) // Send to pattern editor emit currentOrderChanged(curPos_.row); backChanged_ = true; textChanged_ = true; repaint(); } void OrderListPanel::changeEditable() { backChanged_ = true; repaint(); } void OrderListPanel::updatePositionByOrderUpdate(bool isFirstUpdate) { int prev = std::exchange(playingRow_, bt_->getPlayingOrderNumber()); if (!config_->getFollowMode() && prev != playingRow_) { // Repaint only background backChanged_ = true; repaint(); return; } int tmp = std::exchange(curPos_.row, bt_->getCurrentOrderNumber()); int d = curPos_.row - tmp; if (!d) return; emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); // Redraw entire area in first update and jumping order orderDownCount_ = (isFirstUpdate || d < 0 || viewedRowCnt_ < d) ? 0 : d; textChanged_ = true; backChanged_ = true; repaint(); } int OrderListPanel::getScrollableCountByTrack() const { int width = rowNumWidth_; size_t i = songStyle_.trackAttribs.size(); do { --i; width += trackWidth_; if (geometry().width() < width) { return static_cast(i + 1); } } while (i); return 0; } void OrderListPanel::redrawByPatternChanged(bool ordersLengthChanged) { textChanged_ = true; // When length of orders is changed, redraw all area if (ordersLengthChanged) backChanged_ = true; repaint(); } void OrderListPanel::redrawByFocusChanged() { if (hasFocussedBefore_) { backChanged_ = true; repaint(); } else { redrawAll(); hasFocussedBefore_ = true; } } void OrderListPanel::redrawByHoverChanged() { headerChanged_ = true; backChanged_ = true; repaint(); } void OrderListPanel::redrawAll() { backChanged_ = true; textChanged_ = true; headerChanged_ = true; repaint(); } bool OrderListPanel::enterOrder(int key) { switch (key) { case Qt::Key_0: setCellOrderNum(0x0); return true; case Qt::Key_1: setCellOrderNum(0x1); return true; case Qt::Key_2: setCellOrderNum(0x2); return true; case Qt::Key_3: setCellOrderNum(0x3); return true; case Qt::Key_4: setCellOrderNum(0x4); return true; case Qt::Key_5: setCellOrderNum(0x5); return true; case Qt::Key_6: setCellOrderNum(0x6); return true; case Qt::Key_7: setCellOrderNum(0x7); return true; case Qt::Key_8: setCellOrderNum(0x8); return true; case Qt::Key_9: setCellOrderNum(0x9); return true; case Qt::Key_A: setCellOrderNum(0xa); return true; case Qt::Key_B: setCellOrderNum(0xb); return true; case Qt::Key_C: setCellOrderNum(0xc); return true; case Qt::Key_D: setCellOrderNum(0xd); return true; case Qt::Key_E: setCellOrderNum(0xe); return true; case Qt::Key_F: setCellOrderNum(0xf); return true; default: return false; } } void OrderListPanel::setCellOrderNum(int n) { bt_->setOrderPatternDigit(curSongNum_, curPos_.track, curPos_.row, n, (entryCnt_ == 1)); comStack_.lock()->push(new SetPatternToOrderQtCommand(this, curPos_, (entryCnt_ == 1))); entryCnt_ = (entryCnt_ + 1) % 2; if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !entryCnt_) moveCursorToDown(1); } void OrderListPanel::insertOrderBelow() { if (!bt_->canAddNewOrder(curSongNum_)) return; bt_->insertOrderBelow(curSongNum_, curPos_.row); comStack_.lock()->push(new InsertOrderBelowQtCommand(this)); } void OrderListPanel::deleteOrder() { if (bt_->getOrderSize(curSongNum_) > 1) { bt_->deleteOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new DeleteOrderQtCommand(this)); } } void OrderListPanel::copySelectedCells() { if (selLeftAbovePos_.row == -1) return; int w = selRightBelowPos_.track - selLeftAbovePos_.track + 1; int h = selRightBelowPos_.row - selLeftAbovePos_.row + 1; QString str = QString("ORDER_COPY:%1,%2,") .arg(QString::number(w), QString::number(h)); for (int i = 0; i < h; ++i) { std::vector odrs = bt_->getOrderData(curSongNum_, selLeftAbovePos_.row + i); for (int j = 0; j < w; ++j) { str += QString::number(odrs.at(static_cast(selLeftAbovePos_.track + j)).patten); if (i < h - 1 || j < w - 1) str += ","; } } QApplication::clipboard()->setText(str); } void OrderListPanel::pasteCopiedCells(const OrderPosition& startPos) { // Analyze text QString str = QApplication::clipboard()->text().remove(QRegularExpression("ORDER_COPY:")); QString hdRe = "^([0-9]+),([0-9]+),"; QRegularExpression re(hdRe); QRegularExpressionMatch match = re.match(str); int w = match.captured(1).toInt(); size_t h = match.captured(2).toUInt(); str.remove(re); std::vector> cells; re = QRegularExpression("^([^,]+),"); for (size_t i = 0; i < h; ++i) { cells.emplace_back(); for (int j = 0; j < w; ++j) { match = re.match(str); if (match.hasMatch()) { cells.at(i).push_back(match.captured(1).toStdString()); str.remove(re); } else { cells.at(i).push_back(str.toStdString()); break; } } } // Send cells data bt_->pasteOrderCells(curSongNum_, startPos.track, startPos.row, std::move(cells)); comStack_.lock()->push(new PasteCopiedDataToOrderQtCommand(this)); } void OrderListPanel::setSelectedRectangle(const OrderPosition& start, const OrderPosition& end) { if (start.track > end.track) { if (start.row > end.row) { selLeftAbovePos_ = end; selRightBelowPos_ = start; } else { selLeftAbovePos_ = { end.track, start.row }; selRightBelowPos_ = { start.track, end.row }; } } else { if (start.row > end.row) { selLeftAbovePos_ = { start.track, end.row }; selRightBelowPos_ = { end.track, start.row }; } else { selLeftAbovePos_ = start; selRightBelowPos_ = end; } } emit selected(true); backChanged_ = true; repaint(); } bool OrderListPanel::isSelectedCell(int track, int row) { OrderPosition pos{ track, row }; return (selLeftAbovePos_.track <= pos.track && selRightBelowPos_.track >= pos.track && selLeftAbovePos_.row <= pos.row && selRightBelowPos_.row >= pos.row); } void OrderListPanel::showContextMenu(const OrderPosition& pos, const QPoint& point) { QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* insert = menu.addAction(tr("&Insert Order")); insert->setIcon(QIcon(":/icon/insert_order")); QObject::connect(insert, &QAction::triggered, this, [&]() { insertOrderBelow(); }); QAction* remove = menu.addAction(tr("&Remove Order")); remove->setIcon(QIcon(":/icon/remove_order")); QObject::connect(remove, &QAction::triggered, this, [&]() { deleteOrder(); }); QAction* duplicate = menu.addAction(tr("&Duplicate Order")); duplicate->setIcon(QIcon(":/icon/duplicate_order")); QObject::connect(duplicate, &QAction::triggered, this, &OrderListPanel::onDuplicatePressed); QAction* clonep = menu.addAction(tr("&Clone Patterns")); QAction::connect(clonep, &QAction::triggered, this, &OrderListPanel::onClonePatternsPressed); QAction* cloneo = menu.addAction(tr("Clone &Order")); QObject::connect(cloneo, &QAction::triggered, this, &OrderListPanel::onCloneOrderPressed); menu.addSeparator(); QAction* moveUp = menu.addAction(tr("Move Order &Up")); moveUp->setIcon(QIcon(":/icon/order_up")); QObject::connect(moveUp, &QAction::triggered, this, [&]() { onMoveOrderPressed(true); }); QAction* moveDown = menu.addAction(tr("Move Order Do&wn")); moveDown->setIcon(QIcon(":/icon/order_down")); QObject::connect(moveDown, &QAction::triggered, this, [&]() { onMoveOrderPressed(false); }); menu.addSeparator(); QAction* copy = menu.addAction(tr("Cop&y")); copy->setIcon(QIcon(":/icon/copy")); QObject::connect(copy, &QAction::triggered, this, &OrderListPanel::copySelectedCells); QAction* paste = menu.addAction(tr("&Paste")); paste->setIcon(QIcon(":/icon/paste")); QObject::connect(paste, &QAction::triggered, this, [&]() { pasteCopiedCells(pos); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) duplicate->setShortcutVisibleInContextMenu(true); clonep->setShortcutVisibleInContextMenu(true); copy->setShortcutVisibleInContextMenu(true); paste->setShortcutVisibleInContextMenu(true); #endif duplicate->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); clonep->setShortcut(QKeySequence(Qt::ALT + Qt::Key_D)); copy->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C)); paste->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V)); if (pos.row < 0 || pos.track < 0) { remove->setEnabled(false); moveUp->setEnabled(false); moveDown->setEnabled(false); copy->setEnabled(false); paste->setEnabled(false); } if (!bt_->canAddNewOrder(curSongNum_)) { insert->setEnabled(false); duplicate->setEnabled(false); moveUp->setEnabled(false); moveDown->setEnabled(false); copy->setEnabled(false); paste->setEnabled(false); } QString clipText = QApplication::clipboard()->text(); if (!clipText.startsWith("ORDER_COPY")) { paste->setEnabled(false); } if (bt_->getOrderSize(curSongNum_) == 1) { remove->setEnabled(false); } if (selRightBelowPos_.row < 0 || !isSelectedCell(pos.track, pos.row)) { clonep->setEnabled(false); copy->setEnabled(false); } if (pos.row == 0) { moveUp->setEnabled(false); } if (pos.row == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { moveDown->setEnabled(false); } menu.exec(mapToGlobal(point)); } /********** Slots **********/ void OrderListPanel::onHScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (config_->getMoveCursorByHorizontalScroll()) { if (int dif = num - curPos_.track) moveCursorToRight(dif); } else { if (int dif = num - leftTrackNum_) moveViewToRight(dif); } } void OrderListPanel::onVScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (int dif = num - curPos_.row) moveCursorToDown(dif); } void OrderListPanel::setCurrentTrack(int num) { Ui::EventGuard eg(isIgnoreToPattern_); // Skip if position has already changed in panel if (int dif = num - curPos_.track) moveCursorToRight(dif); } void OrderListPanel::setCurrentOrder(int num) { Ui::EventGuard eg(isIgnoreToPattern_); // Skip if position has already changed in panel if (int dif = num - curPos_.row) moveCursorToDown(dif); } void OrderListPanel::onOrderEdited() { // Move cursor int s = static_cast(bt_->getOrderSize(curSongNum_)); if (s <= curPos_.row) { curPos_.row = s - 1; bt_->setCurrentOrderNumber(curPos_.row); } emit orderEdited(); } void OrderListPanel::onSongLoaded() { curSongNum_ = bt_->getCurrentSongNumber(); curPos_ = { bt_->getCurrentTrackAttribute().number, bt_->getCurrentOrderNumber() }; songStyle_ = bt_->getSongStyle(curSongNum_); leftTrackNum_ = 0; updateTracksWidthFromLeftToEnd(); setMaximumWidth(columnsWidthFromLeftToEnd_); hovPos_ = { -1, -1 }; mousePressPos_ = { -1, -1 }; mouseReleasePos_ = { -1, -1 }; selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; shiftPressedPos_ = { -1, -1 }; entryCnt_ = 0; selectAllState_ = -1; emit selected(false); redrawAll(); } void OrderListPanel::onPastePressed() { pasteCopiedCells(curPos_); } void OrderListPanel::onSelectPressed(int type) { switch (type) { case 0: // None { selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; selectAllState_ = -1; emit selected(false); backChanged_ = true; repaint(); break; } case 1: // All { int max = static_cast(bt_->getOrderSize(curSongNum_)) - 1; selectAllState_ = (selectAllState_ + 1) % 2; if (!selectAllState_) { OrderPosition start = { curPos_.track, 0 }; OrderPosition end = { curPos_.track, max }; setSelectedRectangle(start, end); } else { OrderPosition start = { 0, 0 }; OrderPosition end = { static_cast(songStyle_.trackAttribs.size() - 1), max }; setSelectedRectangle(start, end); } break; } case 2: // Row { selectAllState_ = -1; OrderPosition start = { 0, curPos_.row }; OrderPosition end = { static_cast(songStyle_.trackAttribs.size() - 1), curPos_.row }; setSelectedRectangle(start, end); break; } case 3: // Column { selectAllState_ = -1; OrderPosition start = { curPos_.track, 0 }; OrderPosition end = { curPos_.track, static_cast(bt_->getOrderSize(curSongNum_) - 1) }; setSelectedRectangle(start, end); break; } case 4: // Pattern { selectAllState_ = -1; setSelectedRectangle(curPos_, curPos_); break; } case 5: // Order { onSelectPressed(2); break; } } } void OrderListPanel::onDuplicatePressed() { bt_->duplicateOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new DuplicateOrderQtCommand(this)); } void OrderListPanel::onMoveOrderPressed(bool isUp) { if ((isUp && curPos_.row == 0) || (!isUp && curPos_.row == static_cast(bt_->getOrderSize(curSongNum_)) - 1)) return; bt_->MoveOrder(curSongNum_, curPos_.row, isUp); comStack_.lock()->push(new MoveOrderQtCommand(this)); } void OrderListPanel::onClonePatternsPressed() { if (selLeftAbovePos_.row == -1) return; bt_->clonePatterns(curSongNum_, selLeftAbovePos_.row, selLeftAbovePos_.track, selRightBelowPos_.row, selRightBelowPos_.track); comStack_.lock()->push(new ClonePatternsQtCommand(this)); } void OrderListPanel::onCloneOrderPressed() { bt_->cloneOrder(curSongNum_, curPos_.row); comStack_.lock()->push(new CloneOrderQtCommand(this)); } void OrderListPanel::onFollowModeChanged() { curPos_.row = bt_->getCurrentOrderNumber(); emit vScrollBarChangeRequested(curPos_.row, static_cast(bt_->getOrderSize(curSongNum_)) - 1); // Force redraw all area followModeChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } void OrderListPanel::onStoppedPlaySong() { followModeChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } /********** Events **********/ bool OrderListPanel::event(QEvent *event) { switch (event->type()) { case QEvent::KeyPress: return keyPressed(dynamic_cast(event)); case QEvent::KeyRelease: return keyReleased(dynamic_cast(event)); case QEvent::HoverMove: return mouseHoverd(dynamic_cast(event)); default: return QWidget::event(event); } } bool OrderListPanel::keyPressed(QKeyEvent *event) { /* General Keys */ switch (event->key()) { case Qt::Key_Return: emit returnPressed(); return true; case Qt::Key_Shift: shiftPressedPos_ = curPos_; return true; case Qt::Key_Left: moveCursorToRight(-1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Right: moveCursorToRight(1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Up: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Down: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Home: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-curPos_.row); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_End: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown( static_cast(bt_->getOrderSize(curSongNum_)) - curPos_.row - 1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageUp: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageDown: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Insert: insertOrderBelow(); return true; case Qt::Key_Menu: showContextMenu( curPos_, QPoint(calculateColumnsWidthWithRowNum(leftTrackNum_, curPos_.track), curRowY_ - 8)); return true; default: if (event->modifiers().testFlag(Qt::NoModifier)) { return enterOrder(event->key()); } return false; } } bool OrderListPanel::keyReleased(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = { -1, -1 }; return true; default: return false; } } void OrderListPanel::paintEvent(QPaintEvent *event) { if (bt_) { const QRect& area = event->rect(); if (area.x() == 0 && area.y() == 0) { drawList(area); } else { drawList(rect()); } } } void OrderListPanel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); // Recalculate center row position curRowBaselineY_ = (geometry().height() + headerHeight_) / 2; curRowY_ = curRowBaselineY_ + rowFontLeading_ / 2 - rowFontAscent_; initDisplay(); redrawAll(); } void OrderListPanel::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event) setFocus(); mousePressPos_ = hovPos_; mouseReleasePos_ = { -1, -1 }; if (event->button() == Qt::LeftButton) { selLeftAbovePos_ = { -1, -1 }; selRightBelowPos_ = { -1, -1 }; selectAllState_ = -1; emit selected(false); } } void OrderListPanel::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { if (mousePressPos_.track < 0 || mousePressPos_.row < 0) return; // Start point is out of range if (hovPos_.track >= 0) { setSelectedRectangle(mousePressPos_, hovPos_); } if (event->x() < rowNumWidth_ && leftTrackNum_ > 0) { if (config_->getMoveCursorByHorizontalScroll()) moveCursorToRight(-1); else moveViewToRight(-1); } else if (event->x() > geometry().width() - rowNumWidth_ && hovPos_.track != -1) { if (config_->getMoveCursorByHorizontalScroll()) moveCursorToRight(1); else moveViewToRight(1); } if (event->pos().y() < headerHeight_ + rowFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } else if (event->pos().y() > geometry().height() - rowFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(1); } } } void OrderListPanel::mouseReleaseEvent(QMouseEvent* event) { mouseReleasePos_ = hovPos_; switch (event->button()) { case Qt::LeftButton: if (mousePressPos_ == mouseReleasePos_) { // Jump cell if (hovPos_.row >= 0 && hovPos_.track >= 0) { int horDif = hovPos_.track - curPos_.track; int verDif = hovPos_.row - curPos_.row; moveCursorToRight(horDif); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(verDif); } else if (hovPos_.row == -2 && hovPos_.track >= 0) { // Header int horDif = hovPos_.track - curPos_.track; moveCursorToRight(horDif); } else if (hovPos_.track == -2 && hovPos_.row >= 0) { // Row number if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int verDif = hovPos_.row - curPos_.row; moveCursorToDown(verDif); } } } break; case Qt::RightButton: showContextMenu(mousePressPos_, event->pos()); break; case Qt::XButton1: if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { moveCursorToDown(-1); } break; case Qt::XButton2: if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { moveCursorToDown(1); } break; default: break; } mousePressPos_ = { -1, -1 }; mouseReleasePos_ = { -1, -1 }; } bool OrderListPanel::mouseHoverd(QHoverEvent *event) { QPoint pos = event->pos(); OrderPosition oldPos = hovPos_; // Detect row if (pos.y() <= headerHeight_) { hovPos_.row = -2; // Header } else { if (pos.y() < curRowY_) { int tmp = curPos_.row + (pos.y() - curRowY_) / rowFontHeight_ - 1; hovPos_.row = (tmp < 0) ? -1 : tmp; } else { hovPos_.row = curPos_.row + (pos.y() - curRowY_) / rowFontHeight_; if (hovPos_.row >= static_cast(bt_->getOrderSize(curSongNum_))) hovPos_.row = -1; } } // Detect track if (pos.x() <= rowNumWidth_) { hovPos_.track = -2; // Row number } else { int tmpWidth = rowNumWidth_; for (int i = leftTrackNum_; ; ) { tmpWidth += trackWidth_; if (pos.x() <= tmpWidth) { hovPos_.track = i; break; } ++i; if (i == static_cast(songStyle_.trackAttribs.size())) { hovPos_.track = -1; break; } } } if (hovPos_ != oldPos) redrawByHoverChanged(); return true; } void OrderListPanel::wheelEvent(QWheelEvent *event) { if (bt_->isPlaySong() && bt_->isFollowPlay()) return; int degree = event->angleDelta().y() / 8; moveCursorToDown(-degree / 15); } void OrderListPanel::leaveEvent(QEvent* event) { Q_UNUSED(event) // Clear mouse hover selection hovPos_ = { -1, -1 }; } BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_list_panel.hpp000066400000000000000000000122261362177441300273160ustar00rootroot00000000000000#ifndef ORDER_LIST_PANEL_HPP #define ORDER_LIST_PANEL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/order_list_editor/order_position.hpp" #include "song.hpp" #include "gui/color_palette.hpp" class OrderListPanel : public QWidget { Q_OBJECT public: explicit OrderListPanel(QWidget *parent = nullptr); void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); void updatePositionByOrderUpdate(bool isFirstUpdate); int getScrollableCountByTrack() const; void copySelectedCells(); void deleteOrder(); void insertOrderBelow(); void redrawByPatternChanged(bool ordersLengthChanged = false); void redrawByFocusChanged(); void redrawByHoverChanged(); void redrawAll(); void resetEntryCount(); void freeze(); void unfreeze(); QString getHeaderFont() const; int getHeaderFontSize() const; QString getRowsFont() const; int getRowsFontSize() const; void setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize); public slots: void onHScrollBarChanged(int num); void onVScrollBarChanged(int num); void setCurrentTrack(int num); void setCurrentOrder(int num); void onOrderEdited(); void onSongLoaded(); void onPastePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onDuplicatePressed(); void onMoveOrderPressed(bool isUp); void onClonePatternsPressed(); void onCloneOrderPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); signals: void hScrollBarChangeRequested(int num); void vScrollBarChangeRequested(int num, int max); void currentTrackChanged(int num); void currentOrderChanged(int num); void orderEdited(); void selected(bool isSelected); void returnPressed(); protected: virtual bool event(QEvent *event) override; bool keyPressed(QKeyEvent* event); bool keyReleased(QKeyEvent* event); virtual void paintEvent(QPaintEvent* event) override; virtual void resizeEvent(QResizeEvent* event) override; virtual void mousePressEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; bool mouseHoverd(QHoverEvent* event); virtual void wheelEvent(QWheelEvent* event) override; virtual void leaveEvent(QEvent* event) override; private: std::unique_ptr completePixmap_, textPixmap_, backPixmap_, headerPixmap_; std::shared_ptr bt_; std::weak_ptr comStack_; std::shared_ptr config_; std::shared_ptr palette_; QFont rowFont_, headerFont_; std::unique_ptr hdFontMets_; int rowFontWidth_, rowFontHeight_, rowFontAscent_, rowFontLeading_; int headerFontAscent_; int widthSpace_; int rowNumWidthCnt_, rowNumWidth_, rowNumBase_; int trackWidth_; int columnsWidthFromLeftToEnd_; int headerHeight_; int curRowBaselineY_; int curRowY_; int leftTrackNum_; SongStyle songStyle_; int curSongNum_; OrderPosition curPos_, hovPos_; OrderPosition mousePressPos_, mouseReleasePos_; OrderPosition selLeftAbovePos_, selRightBelowPos_; OrderPosition shiftPressedPos_; bool isIgnoreToSlider_, isIgnoreToPattern_; int entryCnt_; int selectAllState_; int viewedRowCnt_; int viewedRegionHeight_; int viewedRowsHeight_, viewedRowOffset_, viewedCenterY_, viewedCenterBaseY_; OrderPosition viewedFirstPos_, viewedCenterPos_, viewedLastPos_; bool backChanged_, textChanged_, headerChanged_, followModeChanged_; bool hasFocussedBefore_; int orderDownCount_; bool freezed_; std::atomic_bool repaintable_; // Recurrensive repaint guard std::atomic_int repaintingCnt_; int playingRow_; void updateSizes(); void initDisplay(); void drawList(const QRect& rect); void drawRows(int maxWidth, int trackSize); void quickDrawRows(int maxWidth, int trackSize); void drawHeaders(int maxWidth, int trackSize); void drawBorders(int maxWidth, int trackSize); void drawShadow(); inline int calculateColumnsWidthWithRowNum(int begin, int end) const { return rowNumWidth_ + trackWidth_ * (end - begin + 1); } inline void updateTracksWidthFromLeftToEnd() { columnsWidthFromLeftToEnd_ = calculateColumnsWidthWithRowNum( leftTrackNum_, static_cast(songStyle_.trackAttribs.size()) - 1); } void moveCursorToRight(int n); void moveViewToRight(int n); void moveCursorToDown(int n); bool enterOrder(int key); void setCellOrderNum(int n); void pasteCopiedCells(const OrderPosition& startPos); void setSelectedRectangle(const OrderPosition& start, const OrderPosition& end); bool isSelectedCell(int track, int row); void showContextMenu(const OrderPosition& pos, const QPoint& point); }; #endif // ORDER_LIST_PANEL_HPP BambooTracker-0.3.5/BambooTracker/gui/order_list_editor/order_position.hpp000066400000000000000000000007561362177441300270350ustar00rootroot00000000000000#ifndef ORDER_POSITION_HPP #define ORDER_POSITION_HPP struct OrderPosition { int track, row; friend bool operator==(const OrderPosition& a, const OrderPosition& b); friend bool operator!=(const OrderPosition& a, const OrderPosition& b); }; inline bool operator==(const OrderPosition& a, const OrderPosition& b) { return (a.track == b.track && a.row == b.row); } inline bool operator!=(const OrderPosition& a, const OrderPosition& b) { return !(a == b); } #endif // ORDER_POSITION_HPP BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/000077500000000000000000000000001362177441300225645ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_editor.cpp000066400000000000000000000201611362177441300263130ustar00rootroot00000000000000#include "pattern_editor.hpp" #include "ui_pattern_editor.h" PatternEditor::PatternEditor(QWidget *parent) : QFrame(parent), ui(new Ui::PatternEditor), freezed_(false), songLoaded_(false), hScrollCellMove_(true) { ui->setupUi(this); installEventFilter(this); ui->panel->installEventFilter(this); ui->verticalScrollBar->installEventFilter(this); ui->panel->setFocus(); QObject::connect(ui->panel, &PatternEditorPanel::hScrollBarChangeRequested, ui->horizontalScrollBar, &QScrollBar::setValue); QObject::connect(ui->panel, &PatternEditorPanel::vScrollBarChangeRequested, this, [&](int num, int max) { if (ui->verticalScrollBar->maximum() < num) { ui->verticalScrollBar->setMaximum(max); ui->verticalScrollBar->setValue(num); } else { ui->verticalScrollBar->setValue(num); ui->verticalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &PatternEditorPanel::currentTrackChanged, this, [&](int num) { emit currentTrackChanged(num); }); QObject::connect(ui->panel, &PatternEditorPanel::currentOrderChanged, this, [&](int num, int max) { emit currentOrderChanged(num, max); }); QObject::connect(ui->panel, &PatternEditorPanel::effectColsCompanded, this, [&](int num, int max) { if (ui->horizontalScrollBar->maximum() < num) { ui->horizontalScrollBar->setMaximum(max); ui->horizontalScrollBar->setValue(num); } else { ui->horizontalScrollBar->setValue(num); ui->horizontalScrollBar->setMaximum(max); } }); QObject::connect(ui->panel, &PatternEditorPanel::selected, this, [&](bool isSelected) { emit selected(isSelected); }); QObject::connect(ui->panel, &PatternEditorPanel::returnPressed, this, [&] { emit returnPressed(); }); QObject::connect(ui->panel, &PatternEditorPanel::instrumentEntered, this, [&](int num) { emit instrumentEntered(num); }); QObject::connect(ui->panel, &PatternEditorPanel::effectEntered, this, [&](QString text) { emit effectEntered(text); }); auto focusSlot = [&] { ui->panel->setFocus(); }; QObject::connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->panel, &PatternEditorPanel::onHScrollBarChanged); QObject::connect(ui->horizontalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); QObject::connect(ui->verticalScrollBar, &QScrollBar::valueChanged, ui->panel, &PatternEditorPanel::onVScrollBarChanged); QObject::connect(ui->verticalScrollBar, &QScrollBar::sliderPressed, this, focusSlot); } PatternEditor::~PatternEditor() { delete ui; } void PatternEditor::setCore(std::shared_ptr core) { bt_ = core; ui->panel->setCore(core); } void PatternEditor::setCommandStack(std::weak_ptr stack) { ui->panel->setCommandStack(stack); } void PatternEditor::setConfiguration(std::shared_ptr config) { ui->panel->setConfiguration(config); } void PatternEditor::setColorPallete(std::shared_ptr palette) { ui->panel->setColorPallete(palette); } void PatternEditor::changeEditable() { ui->panel->changeEditable(); } void PatternEditor::updatePositionByStepUpdate(bool isFirstUpdate) { ui->panel->updatePositionByStepUpdate(isFirstUpdate); } void PatternEditor::copySelectedCells() { ui->panel->copySelectedCells(); } void PatternEditor::cutSelectedCells() { ui->panel->cutSelectedCells(); } void PatternEditor::freeze() { freezed_ = true; ui->panel->freeze(); } void PatternEditor::unfreeze() { freezed_ = false; ui->panel->unfreeze(); } QString PatternEditor::getHeaderFont() const { return ui->panel->getHeaderFont(); } int PatternEditor::getHeaderFontSize() const { return ui->panel->getHeaderFontSize(); } QString PatternEditor::getRowsFont() const { return ui->panel->getRowsFont(); } int PatternEditor::getRowsFontSize() const { return ui->panel->getRowsFontSize(); } void PatternEditor::setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize) { ui->panel->setFonts(headerFont, headerSize, rowsFont, rowsSize); } void PatternEditor::setHorizontalScrollMode(bool cellBased, bool refresh) { hScrollCellMove_ = cellBased; if (refresh) updateHorizontalSliderMaximum(); } bool PatternEditor::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) if (watched == this) { if (event->type() == QEvent::FocusIn) { ui->panel->setFocus(); } return false; } if (watched == ui->panel) { if (freezed_ && event->type() != QEvent::Paint) return true; switch (event->type()) { case QEvent::FocusIn: ui->panel->redrawByFocusChanged(); emit focusIn(); return false; case QEvent::FocusOut: ui->panel->redrawByFocusChanged(); emit focusOut(); return false; case QEvent::HoverEnter: case QEvent::HoverLeave: ui->panel->redrawByHoverChanged(); return false; default: return false; } } if (watched == ui->verticalScrollBar) { if (freezed_) return true; // Ignore every events switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::DragMove: case QEvent::Wheel: return (bt_->isPlaySong() && bt_->isFollowPlay()); default: return false; } } return false; } void PatternEditor::resizeEvent(QResizeEvent* event) { Q_UNUSED(event) // For view-based scroll updateHorizontalSliderMaximum(); } /********** Slots **********/ void PatternEditor::setCurrentTrack(int num) { ui->panel->setCurrentTrack(num); } void PatternEditor::setCurrentOrder(int num) { ui->panel->setCurrentOrder(num); } void PatternEditor::onOrderListEdited() { ui->panel->onOrderListEdited(); } void PatternEditor::onDefaultPatternSizeChanged() { ui->panel->onDefaultPatternSizeChanged(); } void PatternEditor::setPatternHighlight1Count(int count) { ui->panel->setPatternHighlight1Count(count); } void PatternEditor::setPatternHighlight2Count(int count) { ui->panel->setPatternHighlight2Count(count); } void PatternEditor::setEditableStep(int n) { ui->panel->setEditableStep(n); } void PatternEditor::onSongLoaded() { ui->horizontalScrollBar->setValue(0); ui->panel->onSongLoaded(); songLoaded_ = true; updateHorizontalSliderMaximum(); ui->verticalScrollBar->setMaximum(static_cast(bt_->getPatternSizeFromOrderNumber( bt_->getCurrentSongNumber(), bt_->getCurrentOrderNumber())) - 1); ui->verticalScrollBar->setValue(0); } void PatternEditor::onDeletePressed() { ui->panel->onDeletePressed(); } void PatternEditor::onPastePressed() { ui->panel->onPastePressed(); } void PatternEditor::onPasteMixPressed() { ui->panel->onPasteMixPressed(); } void PatternEditor::onPasteOverwritePressed() { ui->panel->onPasteOverwritePressed(); } void PatternEditor::onSelectPressed(int type) { ui->panel->onSelectPressed(type); } void PatternEditor::onTransposePressed(bool isOctave, bool isIncreased) { ui->panel->onTransposePressed(isOctave, isIncreased); } void PatternEditor::onToggleTrackPressed() { ui->panel->onToggleTrackPressed(ui->panel->getCurrentTrack()); } void PatternEditor::onSoloTrackPressed() { ui->panel->onSoloTrackPressed(ui->panel->getCurrentTrack()); } void PatternEditor::onExpandEffectColumn() { ui->panel->onExpandEffectColumnPressed(ui->panel->getCurrentTrack()); } void PatternEditor::onShrinkEffectColumn() { ui->panel->onShrinkEffectColumnPressed(ui->panel->getCurrentTrack()); } void PatternEditor::onExpandPressed() { ui->panel->onExpandPressed(); } void PatternEditor::onShrinkPressed() { ui->panel->onShrinkPressed(); } void PatternEditor::onInterpolatePressed() { ui->panel->onInterpolatePressed(); } void PatternEditor::onReversePressed() { ui->panel->onReversePressed(); } void PatternEditor::onReplaceInstrumentPressed() { ui->panel->onReplaceInstrumentPressed(); } void PatternEditor::onFollowModeChanged() { ui->panel->onFollowModeChanged(); } void PatternEditor::onStoppedPlaySong() { ui->panel->redrawPatterns(); } void PatternEditor::onDuplicateInstrumentsRemoved() { ui->panel->redrawByPatternChanged(); } void PatternEditor::updateHorizontalSliderMaximum() { if (!ui->panel->isReadyCore() || !songLoaded_) return; int max = hScrollCellMove_ ? ui->panel->getFullColmunSize() : ui->panel->getScrollableCountByTrack(); ui->horizontalScrollBar->setMaximum(max); } BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_editor.hpp000066400000000000000000000050271362177441300263240ustar00rootroot00000000000000#ifndef PATTERN_EDITOR_HPP #define PATTERN_EDITOR_HPP #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "gui/color_palette.hpp" namespace Ui { class PatternEditor; } class PatternEditor : public QFrame { Q_OBJECT public: explicit PatternEditor(QWidget *parent = nullptr); ~PatternEditor() override; void setCore(std::shared_ptr core); void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); void updatePositionByStepUpdate(bool isFirstUpdate); void copySelectedCells(); void cutSelectedCells(); void freeze(); void unfreeze(); QString getHeaderFont() const; int getHeaderFontSize() const; QString getRowsFont() const; int getRowsFontSize() const; void setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize); void setHorizontalScrollMode(bool cellBased, bool refresh = true); signals: void currentTrackChanged(int num); void currentOrderChanged(int num, int max); void focusIn(); void focusOut(); void selected(bool isSelected); void returnPressed(); void instrumentEntered(int num); void effectEntered(QString text); protected: bool eventFilter(QObject *watched, QEvent *event) override; void resizeEvent(QResizeEvent* event) override; public slots: void setCurrentTrack(int num); void setCurrentOrder(int num); void onOrderListEdited(); void onDefaultPatternSizeChanged(); void setPatternHighlight1Count(int count); void setPatternHighlight2Count(int count); void setEditableStep(int n); void onSongLoaded(); void onDeletePressed(); void onPastePressed(); void onPasteMixPressed(); void onPasteOverwritePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onTransposePressed(bool isOctave, bool isIncreased); void onToggleTrackPressed(); void onSoloTrackPressed(); void onExpandEffectColumn(); void onShrinkEffectColumn(); void onExpandPressed(); void onShrinkPressed(); void onInterpolatePressed(); void onReversePressed(); void onReplaceInstrumentPressed(); void onFollowModeChanged(); void onStoppedPlaySong(); void onDuplicateInstrumentsRemoved(); private: Ui::PatternEditor *ui; std::shared_ptr bt_; bool freezed_; bool songLoaded_; bool hScrollCellMove_; void updateHorizontalSliderMaximum(); }; #endif // PATTERN_EDITOR_HPP BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_editor.ui000066400000000000000000000033461362177441300261540ustar00rootroot00000000000000 PatternEditor 0 0 400 300 Frame QFrame::Panel QFrame::Sunken 0 0 0 0 0 Qt::Horizontal Qt::Vertical PatternEditorPanel QWidget
gui/pattern_editor/pattern_editor_panel.hpp
1
BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_editor_panel.cpp000066400000000000000000003034561362177441300275050ustar00rootroot00000000000000#include "pattern_editor_panel.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gui/event_guard.hpp" #include "gui/command/pattern/pattern_commands_qt.hpp" #include "midi/midi.hpp" #include "jam_manager.hpp" #include "gui/effect_description.hpp" PatternEditorPanel::PatternEditorPanel(QWidget *parent) : QWidget(parent), config_(std::make_shared()), // Dummy stepFontWidth_(0), stepFontHeight_(0), stepFontAscent_(0), stepFontLeading_(0), headerFontAscent_(0), widthSpace_(0), widthSpaceDbl_(0), stepNumWidthCnt_(0), stepNumWidth_(0), stepNumBase_(0), baseTrackWidth_(0), toneNameWidth_(0), instWidth_(0), volWidth_(0), effWidth_(0), effIDWidth_(0), effValWidth_(0), tracksWidthFromLeftToEnd_(0), hdMuteToggleWidth_(0), hdEffCompandButtonWidth_(0), headerHeight_(0), hdPlusY_(0), hdMinusY_(0), curRowBaselineY_(0), curRowY_(0), leftTrackNum_(0), curSongNum_(0), curPos_{ 0, 0, 0, 0, }, hovPos_{ -1, -1, -1, -1 }, mousePressPos_{ -1, -1, -1, -1 }, mouseReleasePos_{ -1, -1, -1, -1 }, selLeftAbovePos_{ -1, -1, -1, -1 }, selRightBelowPos_{ -1, -1, -1, -1 }, shiftPressedPos_{ -1, -1, -1, -1 }, doubleClickPos_{ -1, -1, -1, -1 }, isIgnoreToSlider_(false), isIgnoreToOrder_(false), isPressedPlus_(false), isPressedMinus_(false), entryCnt_(0), selectAllState_(-1), isMuteElse_(false), hl1Cnt_(4), hl2Cnt_(16), editableStepCnt_(1), viewedRowCnt_(1), viewedRowsHeight_(0), viewedRowOffset_(0), viewedCenterY_(0), viewedCenterBaseY_(0), backChanged_(false), textChanged_(false), foreChanged_(false), headerChanged_(false), focusChanged_(false), followModeChanged_(false), hasFocussedBefore_(false), stepDownCount_(0), freezed_(false), repaintable_(true), repaintingCnt_(0) { // Initialize font headerFont_ = QApplication::font(); headerFont_.setPointSize(10); stepFont_ = QFont("Monospace", 10); stepFont_.setStyleHint(QFont::TypeWriter); stepFont_.setStyleStrategy(QFont::ForceIntegerMetrics); updateSizes(); rightEffn_ = std::vector(15); setAttribute(Qt::WA_Hover); setContextMenuPolicy(Qt::CustomContextMenu); midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)"); Q_ASSERT(midiKeyEventMethod_ != -1); MidiInterface::instance().installInputHandler(&midiThreadReceivedEvent, this); } PatternEditorPanel::~PatternEditorPanel() { MidiInterface::instance().uninstallInputHandler(&midiThreadReceivedEvent, this); } void PatternEditorPanel::updateSizes() { QFontMetrics metrics(stepFont_); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) stepFontWidth_ = metrics.horizontalAdvance('0'); #else stepFontWidth_ = metrics.width('0'); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) stepFontAscent_ = metrics.capHeight(); #else stepFontAscent_ = metrics.boundingRect('X').height(); #endif stepFontLeading_ = metrics.ascent() - stepFontAscent_ + metrics.descent() / 2; stepFontHeight_ = stepFontAscent_ + stepFontLeading_; QFontMetrics m(headerFont_); headerFontAscent_ = m.ascent() + 2; /* Width & height */ widthSpace_ = stepFontWidth_ / 5 * 2; widthSpaceDbl_ = widthSpace_ * 2; stepNumWidthCnt_ = config_->getShowRowNumberInHex() ? 2 : 3; if (config_->getShowRowNumberInHex()) { stepNumWidthCnt_ = 2; stepNumBase_ = 16; } else { stepNumWidthCnt_ = 3; stepNumBase_ = 10; } stepNumWidth_ = stepFontWidth_ * stepNumWidthCnt_ + widthSpace_; toneNameWidth_ = stepFontWidth_ * 3; instWidth_ = stepFontWidth_ * 2; volWidth_ = stepFontWidth_ * 2; effIDWidth_ = stepFontWidth_ * 2; effValWidth_ = stepFontWidth_ * 2; effWidth_ = effIDWidth_ + effValWidth_ + widthSpaceDbl_; baseTrackWidth_ = toneNameWidth_ + instWidth_ + volWidth_ + effIDWidth_ + effValWidth_ + widthSpaceDbl_ * 4; #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) hdEffCompandButtonWidth_ = m.horizontalAdvance("+"); #else hdEffCompandButtonWidth_ = m.width("+"); #endif hdMuteToggleWidth_ = baseTrackWidth_ - hdEffCompandButtonWidth_ - stepFontWidth_ / 2 * 3; headerHeight_ = m.height() * 2; hdPlusY_ = headerHeight_ / 4 + m.lineSpacing() / 2 - m.leading() / 2 - m.descent(); hdMinusY_ = headerHeight_ / 2 + hdPlusY_; initDisplay(); } void PatternEditorPanel::initDisplay() { completePixmap_ = std::make_unique(geometry().size()); int width = geometry().width(); // Recalculate pixmap sizes viewedRegionHeight_ = std::max((geometry().height() - headerHeight_), stepFontHeight_); int cnt = viewedRegionHeight_ / stepFontHeight_; viewedRowCnt_ = (cnt % 2) ? (cnt + 2) : (cnt + 1); viewedRowsHeight_ = viewedRowCnt_ * stepFontHeight_; viewedRowOffset_ = (viewedRowsHeight_ - viewedRegionHeight_) >> 1; viewedCenterY_ = (viewedRowsHeight_ - stepFontHeight_) >> 1; viewedCenterBaseY_ = viewedCenterY_ + stepFontAscent_ + (stepFontLeading_ >> 1); backPixmap_ = std::make_unique(width, viewedRowsHeight_); textPixmap_ = std::make_unique(width, viewedRowsHeight_); forePixmap_ = std::make_unique(width, viewedRowsHeight_); headerPixmap_ = std::make_unique(width, headerHeight_); } void PatternEditorPanel::setCore(std::shared_ptr core) { bt_ = core; } bool PatternEditorPanel::isReadyCore() const { return (bt_ != nullptr); } void PatternEditorPanel::setCommandStack(std::weak_ptr stack) { comStack_ = stack; } void PatternEditorPanel::setConfiguration(std::shared_ptr config) { config_ = config; } void PatternEditorPanel::setColorPallete(std::shared_ptr palette) { palette_ = palette; } void PatternEditorPanel::freeze() { freezed_ = true; while (true) { if (repaintingCnt_.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); else { curPos_ = { 0, 0, 0, 0 }; // Init return; } } } void PatternEditorPanel::unfreeze() { freezed_ = false; } QString PatternEditorPanel::getHeaderFont() const { return QFontInfo(headerFont_).family(); } int PatternEditorPanel::getHeaderFontSize() const { return QFontInfo(headerFont_).pointSize(); } QString PatternEditorPanel::getRowsFont() const { return QFontInfo(stepFont_).family(); } int PatternEditorPanel::getRowsFontSize() const { return QFontInfo(stepFont_).pointSize(); } void PatternEditorPanel::setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize) { headerFont_ = QFont(headerFont, headerSize); stepFont_ = QFont(rowsFont, rowsSize); updateSizes(); updateTracksWidthFromLeftToEnd(); redrawAll(); } int PatternEditorPanel::getCurrentTrack() const { return curPos_.track; } void PatternEditorPanel::redrawByPatternChanged(bool patternSizeChanged) { textChanged_ = true; // When pattern size is changed, redraw all area if (patternSizeChanged) { backChanged_ = true; foreChanged_ = true; } repaint(); } void PatternEditorPanel::redrawByFocusChanged() { if (hasFocussedBefore_) { focusChanged_ = true; repaint(); } else { redrawAll(); hasFocussedBefore_ = true; } } void PatternEditorPanel::redrawByHoverChanged() { headerChanged_ = true; backChanged_ = true; repaint(); } void PatternEditorPanel::redrawByMaskChanged() { foreChanged_ = true; headerChanged_ = true; repaint(); } void PatternEditorPanel::redrawPatterns() { backChanged_ = true; textChanged_ = true; foreChanged_ = true; repaint(); } void PatternEditorPanel::redrawAll() { headerChanged_ = true; redrawPatterns(); } void PatternEditorPanel::resetEntryCount() { entryCnt_ = 0; } void PatternEditorPanel::drawPattern(const QRect &rect) { if (!freezed_ && repaintable_.load()) { repaintable_.store(false); ++repaintingCnt_; // Use module data after this line if (backChanged_ || textChanged_ || foreChanged_ || headerChanged_ || focusChanged_ || stepDownCount_ || followModeChanged_) { int maxWidth = std::min(rect.width(), tracksWidthFromLeftToEnd_); int trackSize = static_cast(songStyle_.trackAttribs.size()); completePixmap_->fill(palette_->ptnBackColor); if (!focusChanged_) { if (stepDownCount_ && !followModeChanged_) { quickDrawRows(maxWidth, trackSize); } else { backPixmap_->fill(Qt::transparent); if (textChanged_) textPixmap_->fill(Qt::transparent); if (foreChanged_) forePixmap_->fill(Qt::transparent); drawRows(maxWidth, trackSize); } if (headerChanged_) { // headerPixmap_->fill(Qt::transparent); drawHeaders(maxWidth, trackSize); } } { QPainter mergePainter(completePixmap_.get()); QRect rowsRect(0, viewedRowOffset_, maxWidth, viewedRegionHeight_); QRect inViewRect(0, headerHeight_, maxWidth, viewedRegionHeight_); mergePainter.drawPixmap(inViewRect, *backPixmap_.get(), rowsRect); mergePainter.drawPixmap(inViewRect, *textPixmap_.get(), rowsRect); mergePainter.drawPixmap(inViewRect, *forePixmap_.get(), rowsRect); mergePainter.drawPixmap(headerPixmap_->rect(), *headerPixmap_.get()); } drawBorders(maxWidth, trackSize); if (!hasFocus()) drawShadow(); backChanged_ = false; textChanged_ = false; foreChanged_ = false; headerChanged_ = false; focusChanged_ = false; followModeChanged_ = false; stepDownCount_ = 0; } --repaintingCnt_; // Used module data until this line repaintable_.store(true); } QPainter completePainter(this); completePainter.drawPixmap(rect, *completePixmap_.get()); } void PatternEditorPanel::drawRows(int maxWidth, int trackSize) { QPainter forePainter(forePixmap_.get()); QPainter textPainter(textPixmap_.get()); QPainter backPainter(backPixmap_.get()); textPainter.setFont(stepFont_); int x, trackNum; /* Current row */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor); // Step number if (hovPos_.track == -2 && hovPos_.order == curPos_.order && hovPos_.step == curPos_.step) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { if (curPos_.step % hl2Cnt_) { textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); } else { textPainter.setPen(palette_->ptnHl2StepNumColor); } textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_); ++trackNum; } viewedCenterPos_ = curPos_; int stepNum, odrNum; int rowY, baseY; int playOdrNum = bt_->getPlayingOrderNumber(); int playStepNum = bt_->getPlayingStepNumber(); /* Previous rows */ viewedFirstPos_ = curPos_; for (rowY = viewedCenterY_ - stepFontHeight_, baseY = viewedCenterBaseY_ - stepFontHeight_, stepNum = curPos_.step - 1, odrNum = curPos_.order; rowY >= 0; rowY -= stepFontHeight_, baseY -= stepFontHeight_, --stepNum) { if (stepNum == -1) { if (odrNum == 0) { break; } else if (config_->getShowPreviousNextOrders()) { --odrNum; stepNum = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum)) - 1; } else { break; } } QColor rowColor; if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) { rowColor = palette_->ptnPlayStepColor; } else { rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor); // Step number if (hovPos_.track == -2 && hovPos_.order == odrNum && hovPos_.step == stepNum) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, odrNum, stepNum, x, baseY, rowY); ++trackNum; } if (foreChanged_) { if (odrNum != curPos_.order) // Mask forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); } viewedFirstPos_.setRows(odrNum, stepNum); } int stepEnd = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); /* Next rows */ viewedLastPos_ = curPos_; for (rowY = viewedCenterY_ + stepFontHeight_, baseY = viewedCenterBaseY_ + stepFontHeight_, stepNum = curPos_.step + 1, odrNum = curPos_.order; rowY < viewedRowsHeight_; rowY += stepFontHeight_, baseY += stepFontHeight_, ++stepNum) { if (stepNum == stepEnd) { if (odrNum == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { break; } else if (config_->getShowPreviousNextOrders()) { ++odrNum; stepNum = 0; stepEnd = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum)); } else { break; } } QColor rowColor; if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) { rowColor = palette_->ptnPlayStepColor; } else { rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; } // Fill row backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor); // Step number if (hovPos_.track == -2 && hovPos_.order == odrNum && hovPos_.step == stepNum) backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (textChanged_) { textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); } // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, odrNum, stepNum, x, baseY, rowY); ++trackNum; } if (foreChanged_) { if (odrNum != curPos_.order) // Mask forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); } viewedLastPos_.setRows(odrNum, stepNum); } } void PatternEditorPanel::quickDrawRows(int maxWidth, int trackSize) { int halfRowsCnt = viewedRowCnt_ >> 1; bool repaintForeAll = (curPos_.step - stepDownCount_ < 0); int shift = stepFontHeight_ * stepDownCount_; /* Move up */ QRect srcRect(0, 0, maxWidth, viewedRowsHeight_); if (!repaintForeAll) forePixmap_->scroll(0, -shift, srcRect); textPixmap_->scroll(0, -shift, srcRect); backPixmap_->scroll(0, -shift, srcRect); { PatternPosition fpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, stepDownCount_ - halfRowsCnt); if (fpos.order != -1) viewedFirstPos_ = std::move(fpos); } QPainter forePainter(forePixmap_.get()); QPainter textPainter(textPixmap_.get()); QPainter backPainter(backPixmap_.get()); textPainter.setFont(stepFont_); int x, trackNum; /* Clear previous cursor row, current cursor row and last rows text and foreground */ int prevY = viewedCenterY_ - shift; int lastY = viewedRowsHeight_ - shift; textPainter.setCompositionMode(QPainter::CompositionMode_Source); textPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent); textPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent); textPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); textPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); if (!repaintForeAll) { forePainter.setCompositionMode(QPainter::CompositionMode_Source); forePainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent); forePainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent); forePainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); forePainter.setCompositionMode(QPainter::CompositionMode_SourceOver); } /* Redraw previous cursor step */ { int baseY = viewedCenterBaseY_ - shift; QColor rowColor = !(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepColor : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; // Fill row backPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, rowColor); // Step number if (hovPos_.track == -2 && hovPos_.order == viewedCenterPos_.order && hovPos_.step == viewedCenterPos_.step) backPainter.fillRect(0, prevY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover textPainter.setPen(!(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(viewedCenterPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, viewedCenterPos_.order, viewedCenterPos_.step, x, baseY, prevY); ++trackNum; } } /* Redraw current cursor step */ // Fill row backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor); // Step number if (hovPos_.track == -2 && hovPos_.order == curPos_.order && hovPos_.step == curPos_.step) backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover if (curPos_.step % hl2Cnt_) { textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); } else { textPainter.setPen(palette_->ptnHl2StepNumColor); } textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_); ++trackNum; } viewedCenterPos_ = curPos_; /* Draw new step at last if necessary */ { PatternPosition bpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, halfRowsCnt); if (!config_->getShowPreviousNextOrders() && viewedCenterPos_.order != bpos.order) { // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); } else { int baseY = lastY + (viewedCenterBaseY_ - viewedCenterY_); bpos = std::exchange(viewedLastPos_, bpos); while (true) { if (bpos.compareRows(viewedLastPos_) == 0) break; PatternPosition tmpBpos = calculatePositionFrom(bpos.order, bpos.step, 1); if (tmpBpos.order == -1) { // when viewedLastPos_.row == -1 (viewedlastPos_.row < viewedCenterPos_.row + halRowsCnt) viewedLastPos_ = bpos; // Clear row backPainter.setCompositionMode(QPainter::CompositionMode_Source); backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent); break; } else { bpos = tmpBpos; } QColor rowColor = !(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepColor : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepColor : palette_->ptnDefStepColor; // Fill row backPainter.fillRect(0, lastY, maxWidth, stepFontHeight_, rowColor); // Step number if (hovPos_.track == -2 && hovPos_.order == bpos.order && hovPos_.step == bpos.step) backPainter.fillRect(0, lastY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor); // Paint hover textPainter.setPen(!(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor : palette_->ptnDefStepNumColor); textPainter.drawText(1, baseY, QString("%1").arg(bpos.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper()); // Step data for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { x += drawStep(forePainter, textPainter, backPainter, trackNum, bpos.order, bpos.step, x, baseY, lastY); ++trackNum; } if (bpos.order != curPos_.order) // Mask forePainter.fillRect(0, lastY, maxWidth, stepFontHeight_, palette_->ptnMaskColor); baseY += stepFontHeight_; lastY += stepFontHeight_; } } } /* Redraw foreground all area if new order */ if (repaintForeAll) { forePixmap_->fill(Qt::transparent); int y = viewedCenterY_ - viewedCenterPos_.step * stepFontHeight_; if (y > 0) forePainter.fillRect(0, 0, maxWidth, y, palette_->ptnMaskColor); if (viewedLastPos_.order != viewedCenterPos_.order) { y += static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, viewedCenterPos_.order)) * stepFontHeight_; forePainter.fillRect(0, y, maxWidth, viewedRowsHeight_ - y, palette_->ptnMaskColor); } for (x = stepNumWidth_, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { int w = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(trackNum)); if (foreChanged_ && bt_->isMute(trackNum)) // Paint mute mask forePainter.fillRect(x, 0, w, viewedRowsHeight_, palette_->ptnMaskColor); x += w; ++trackNum; } } } int PatternEditorPanel::drawStep(QPainter &forePainter, QPainter &textPainter, QPainter& backPainter, int trackNum, int orderNum, int stepNum, int x, int baseY, int rowY) { int offset = x + widthSpace_; PatternPosition pos{ trackNum, 0, orderNum, stepNum }; QColor textColor = (orderNum == curPos_.order && stepNum == curPos_.step) ? palette_->ptnCurTextColor : palette_->ptnDefTextColor; bool isHovTrack = (hovPos_.order == -2 && hovPos_.track == trackNum); bool isHovStep = (hovPos_.track == -2 && hovPos_.order == orderNum && hovPos_.step == stepNum); bool isMuteTrack = bt_->isMute(trackNum); SoundSource src = songStyle_.trackAttribs[static_cast(trackNum)].source; /* Tone name */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackNum, 0, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int noteNum = bt_->getStepNoteNumber(curSongNum_, trackNum, orderNum, stepNum); switch (noteNum) { case -1: // None textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "---"); break; case -2: // Key off textPainter.fillRect(offset, rowY + stepFontHeight_ * 2 / 5, toneNameWidth_, stepFontHeight_ / 5, palette_->ptnNoteColor); break; case -3: // Echo 0 textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^0"); break; case -4: // Echo 1 textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^1"); break; case -5: // Echo 2 textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^2"); break; case -6: // Echo 3 textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^3"); break; default: // Convert tone name { QString toneStr; switch (noteNum % 12) { case 0: toneStr = "C-"; break; case 1: toneStr = "C#"; break; case 2: toneStr = "D-"; break; case 3: toneStr = "D#"; break; case 4: toneStr = "E-"; break; case 5: toneStr = "F-"; break; case 6: toneStr = "F#"; break; case 7: toneStr = "G-"; break; case 8: toneStr = "G#"; break; case 9: toneStr = "A-"; break; case 10: toneStr = "A#"; break; case 11: toneStr = "B-"; break; } textPainter.setPen(palette_->ptnNoteColor); textPainter.drawText(offset, baseY, toneStr + QString::number(noteNum / 12)); break; } } } offset += toneNameWidth_ + widthSpaceDbl_; pos.colInTrack = 1; /* Instrument */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackNum, 1, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int instNum = bt_->getStepInstrument(curSongNum_, trackNum, orderNum, stepNum); if (instNum == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { std::unique_ptr inst = bt_->getInstrument(instNum); textPainter.setPen((inst != nullptr && src == inst->getSoundSource()) ? palette_->ptnInstColor : palette_->ptnErrorColor); textPainter.drawText(offset, baseY, QString("%1").arg(instNum, 2, 16, QChar('0')).toUpper()); } } offset += instWidth_ + widthSpaceDbl_; pos.colInTrack = 2; /* Volume */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackNum, 2, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int vol = bt_->getStepVolume(curSongNum_, trackNum, orderNum, stepNum); if (vol == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { int volLim = 0; // Dummy set switch (src) { case SoundSource::FM: volLim = 0x80; break; case SoundSource::SSG: volLim = 0x10; break; case SoundSource::DRUM: volLim = 0x20; break; } textPainter.setPen((vol < volLim) ? palette_->ptnVolColor : palette_->ptnErrorColor); if (src == SoundSource::FM && vol < volLim && config_->getReverseFMVolumeOrder()) { vol = volLim - vol - 1; } textPainter.drawText(offset, baseY, QString("%1").arg(vol, 2, 16, QChar('0')).toUpper()); } } offset += volWidth_ + widthSpaceDbl_; pos.colInTrack = 3; /* Effect */ for (int i = 0; i <= rightEffn_.at(static_cast(trackNum)); ++i) { /* Effect ID */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackNum, pos.colInTrack, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor); std::string effId; QString effStr; if (textChanged_) { effId = bt_->getStepEffectID(curSongNum_, trackNum, orderNum, stepNum, i); effStr = QString::fromStdString(effId); if (effStr == "--") { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, effStr); } else { textPainter.setPen(palette_->ptnEffColor); textPainter.drawText(offset, baseY, effStr); } } offset += effIDWidth_; ++pos.colInTrack; /* Effect Value */ if (pos == curPos_) // Paint current cell backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor); if (pos == hovPos_ || isHovTrack || isHovStep) // Paint hover backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor); if ((selLeftAbovePos_.track >= 0 && selLeftAbovePos_.order >= 0) && isSelectedCell(trackNum, pos.colInTrack, orderNum, stepNum)) // Paint selected backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor); if (textChanged_) { int effVal = bt_->getStepEffectValue(curSongNum_, trackNum, orderNum, stepNum, i); if (effVal == -1) { textPainter.setPen(textColor); textPainter.drawText(offset, baseY, "--"); } else { textPainter.setPen(palette_->ptnEffColor); switch (Effect::toEffectType(src, effId)) { case EffectType::VolumeDelay: if (src == SoundSource::FM && config_->getReverseFMVolumeOrder() && effVal < 0x80) effVal = 0x7f - effVal; break; case EffectType::Brightness: if (config_->getReverseFMVolumeOrder() && effVal > 0) effVal = 0xff - effVal + 1; break; default: break; } if (src == SoundSource::FM && config_->getReverseFMVolumeOrder() && Effect::toEffectType(SoundSource::FM, effId) == EffectType::VolumeDelay) { } textPainter.drawText(offset, baseY, QString("%1").arg(effVal, 2, 16, QChar('0')).toUpper()); } } offset += effValWidth_ + widthSpaceDbl_; ++pos.colInTrack; } if (foreChanged_ && isMuteTrack) // Paint mute mask forePainter.fillRect(x, rowY, offset - x, stepFontHeight_, palette_->ptnMaskColor); return baseTrackWidth_ + effWidth_ * rightEffn_[static_cast(trackNum)]; } void PatternEditorPanel::drawHeaders(int maxWidth, int trackSize) { QPainter painter(headerPixmap_.get()); painter.setFont(headerFont_); painter.fillRect(0, 0, geometry().width(), headerHeight_, palette_->ptnHeaderRowColor); int x, trackNum; int lspace = stepFontWidth_ / 2; for (x = stepNumWidth_ + lspace, trackNum = leftTrackNum_; x < maxWidth && trackNum < trackSize; ) { int tw = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(trackNum)); if (hovPos_.order == -2 && hovPos_.track == trackNum) painter.fillRect(x - lspace, 0, tw, headerHeight_, palette_->ptnHovCellColor); painter.setPen(palette_->ptnHeaderTextColor); QString srcName; const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast(trackNum)]; switch (attrib.source) { case SoundSource::FM: switch (songStyle_.type) { case SongType::Standard: srcName = "FM" + QString::number(attrib.channelInSource + 1); break; case SongType::FM3chExpanded: switch (attrib.channelInSource) { case 2: srcName = "FM3-OP1"; break; case 6: srcName = "FM3-OP2"; break; case 7: srcName = "FM3-OP3"; break; case 8: srcName = "FM3-OP4"; break; default: srcName = "FM" + QString::number(attrib.channelInSource + 1); break; } break; } break; case SoundSource::SSG: srcName = "SSG" + QString::number(attrib.channelInSource + 1); break; case SoundSource::DRUM: switch (attrib.channelInSource) { case 0: srcName = "Bass drum"; break; case 1: srcName = "Snare drum"; break; case 2: srcName = "Top cymbal"; break; case 3: srcName = "Hi-hat"; break; case 4: srcName = "Tom"; break; case 5: srcName = "Rim shot"; break; } break; } painter.drawText(x, headerFontAscent_, srcName); painter.fillRect(x, headerHeight_ - 4, hdMuteToggleWidth_, 2, bt_->isMute(trackNum) ? palette_->ptnMuteColor : palette_->ptnUnmuteColor); painter.drawText(x + hdMuteToggleWidth_ + lspace, hdPlusY_, "+"); painter.drawText(x + hdMuteToggleWidth_ + lspace, hdMinusY_, "-"); x += tw; ++trackNum; } } void PatternEditorPanel::drawBorders(int maxWidth, int trackSize) { Q_UNUSED(trackSize) QPainter painter(completePixmap_.get()); painter.drawLine(0, headerHeight_, geometry().width(), headerHeight_); painter.drawLine(stepNumWidth_, 0, stepNumWidth_, geometry().height()); size_t trackNum = static_cast(leftTrackNum_); for (int x = stepNumWidth_; trackNum < rightEffn_.size(); ) { x += (baseTrackWidth_ + effWidth_ * rightEffn_.at(trackNum)); if (x > maxWidth) break; painter.drawLine(x, 0, x, geometry().height()); ++trackNum; } } void PatternEditorPanel::drawShadow() { QPainter painter(completePixmap_.get()); painter.fillRect(0, 0, geometry().width(), geometry().height(), QColor::fromRgb(0, 0, 0, 47)); } // NOTE: end >= -1 int PatternEditorPanel::calculateTracksWidthWithRowNum(int begin, int end) const { int width = stepNumWidth_; for (int i = begin; i <= end; ++i) { width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(i))); } return width; } int PatternEditorPanel::calculateColNumInRow(int trackNum, int colNumInTrack, bool isExpanded) const { if (isExpanded) { return trackNum * 11 + colNumInTrack; } else { trackNum = std::min(trackNum, static_cast(rightEffn_.size())); return std::accumulate(rightEffn_.begin(), rightEffn_.begin() + trackNum, colNumInTrack, [](int acc, int v) { return acc + 5 + 2 * v; }); } } void PatternEditorPanel::moveCursorToRight(int n) { int oldTrackNum = curPos_.track; bool oldLeftTrack = leftTrackNum_; curPos_.colInTrack += n; if (n > 0) { while (true) { int lim = 5 + 2 * rightEffn_.at(static_cast(curPos_.track)); if (curPos_.colInTrack < lim) { break; } else { if (curPos_.track == static_cast(songStyle_.trackAttribs.size()) - 1) { if (config_->getWarpCursor()) { curPos_.track = 0; } else { curPos_.colInTrack = lim - 1; break; } } else { ++curPos_.track; } curPos_.colInTrack -= lim; } } } else { while (true) { if (curPos_.colInTrack >= 0) { break; } else { if (!curPos_.track) { if (config_->getWarpCursor()) { curPos_.track = static_cast(songStyle_.trackAttribs.size()) - 1; } else { curPos_.colInTrack = 0; break; } } else { --curPos_.track; } curPos_.colInTrack += (5 + 2 * rightEffn_.at(static_cast(curPos_.track))); } } } if (oldTrackNum < curPos_.track) { while (calculateTracksWidthWithRowNum(leftTrackNum_, curPos_.track) > geometry().width()) ++leftTrackNum_; } else { if (curPos_.track < leftTrackNum_) leftTrackNum_ = curPos_.track; } updateTracksWidthFromLeftToEnd(); entryCnt_ = 0; if (curPos_.track != oldTrackNum) bt_->setCurrentTrack(curPos_.track); if (!isIgnoreToSlider_) { if (config_->getMoveCursorByHorizontalScroll()) { emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.track, curPos_.colInTrack)); } else if (curPos_.track != oldTrackNum) { emit hScrollBarChangeRequested(leftTrackNum_); } } if (!isIgnoreToOrder_ && curPos_.track != oldTrackNum) // Send to order list emit currentTrackChanged(curPos_.track); // Request fore-background repaint if leftmost track is changed else request only background repaint if (leftTrackNum_ != oldLeftTrack) { headerChanged_ = true; foreChanged_ = true; textChanged_ = true; } backChanged_ = true; repaint(); } void PatternEditorPanel::moveViewToRight(int n) { leftTrackNum_ += n; updateTracksWidthFromLeftToEnd(); // Calculate cursor position int track = curPos_.track + n; int col = std::min(curPos_.colInTrack, 4 + 2 * rightEffn_.at(static_cast(track))); // Check visible int width = stepNumWidth_; for (int i = leftTrackNum_; i <= track; ++i) { width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast(i))); if (geometry().width() < width) { track = i - 1; col = 4 + 2 * rightEffn_.at(static_cast(track)); break; } } // Move cursor and repaint all headerChanged_ = true; foreChanged_ = true; textChanged_ = true; moveCursorToRight(calculateColumnDistance(curPos_.track, curPos_.colInTrack, track, col)); } void PatternEditorPanel::moveCursorToDown(int n) { int oldOdr = curPos_.order; int tmp = curPos_.step + n; if (n > 0) { while (true) { int dif = tmp - static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); if (dif < 0) { curPos_.step = tmp; break; } else { if (config_->getWarpAcrossOrders()) { if (curPos_.order == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { curPos_.order = 0; } else { ++curPos_.order; } } tmp = dif; } } } else { while (true) { if (tmp < 0) { if (config_->getWarpAcrossOrders()) { if (curPos_.order == 0) { curPos_.order = static_cast(bt_->getOrderSize(curSongNum_)) - 1; } else { --curPos_.order; } } tmp += bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order); } else { curPos_.step = tmp; break; } } } if (curPos_.order != oldOdr) bt_->setCurrentOrderNumber(curPos_.order); bt_->setCurrentStepNumber(curPos_.step); entryCnt_ = 0; if (!isIgnoreToSlider_) emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); if (!isIgnoreToOrder_ && curPos_.order != oldOdr) // Send to order list emit currentOrderChanged( curPos_.order, static_cast(bt_->getOrderSize(curSongNum_)) - 1); backChanged_ = true; textChanged_ = true; foreChanged_ = true; repaint(); } int PatternEditorPanel::calculateColumnDistance(int beginTrack, int beginColumn, int endTrack, int endColumn, bool isExpanded) const { return (calculateColNumInRow(endTrack, endColumn, isExpanded) - calculateColNumInRow(beginTrack, beginColumn, isExpanded)); } int PatternEditorPanel::calculateStepDistance(int beginOrder, int beginStep, int endOrder, int endStep) const { int d = 0; int startOrder, startStep, stopOrder, stopStep; bool flag; if (endOrder >= beginOrder) { startOrder = endOrder; startStep = endStep; stopOrder = beginOrder; stopStep = beginStep; flag = true; } else { startOrder = beginOrder; startStep = beginStep; stopOrder = endOrder; stopStep = endStep; flag = false; } while (true) { if (startOrder == stopOrder) { d += (startStep - stopStep); break; } else { d += startStep; startStep = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, --startOrder)); } } return flag ? d : -d; } PatternPosition PatternEditorPanel::calculatePositionFrom(int order, int step, int by) const { PatternPosition pos{ -1, -1, order, step + by }; if (by > 0) { while (true) { int dif = pos.step - static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order)); if (dif < 0) { break; } else { if (pos.order == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { return { -1, -1, -1, -1 }; } else { ++pos.order; } pos.step = dif; } } } else { while (true) { if (pos.step < 0) { if (pos.order == 0) { return { -1, -1, -1, -1 }; } else { --pos.order; } pos.step += bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order); } else { break; } } } return pos; } QPoint PatternEditorPanel::calculateCurrentCursorPosition() const { int w = calculateTracksWidthWithRowNum(leftTrackNum_, curPos_.track - 1); if (curPos_.colInTrack > 0) { w = w + toneNameWidth_ + widthSpaceDbl_; if (curPos_.colInTrack > 1) { w = w + instWidth_ + widthSpaceDbl_; if (curPos_.colInTrack > 2) { w = w + volWidth_ + widthSpaceDbl_; for (int i = 3; i < 11; ++i) { if (curPos_.colInTrack == i) break; w = w + widthSpace_ + ((i % 2) ? effIDWidth_ : effValWidth_); } } } } return QPoint(w, curRowY_); } int PatternEditorPanel::getScrollableCountByTrack() const { int width = stepNumWidth_; size_t i = songStyle_.trackAttribs.size(); do { --i; width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(i)); if (geometry().width() < width) { return static_cast(i + 1); } } while (i); return 0; } void PatternEditorPanel::changeEditable() { backChanged_ = true; repaint(); } int PatternEditorPanel::getFullColmunSize() const { return calculateColNumInRow(static_cast(songStyle_.trackAttribs.size()) - 1, 4 + 2 * rightEffn_.back()); } void PatternEditorPanel::updatePositionByStepUpdate(bool isFirstUpdate) { if (!config_->getFollowMode()) { // Repaint only background backChanged_ = true; repaint(); return; } PatternPosition tmp = curPos_; curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); int cmp = curPos_.compareRows(tmp); if (!cmp && !isFirstUpdate) return; // Delayed call, already updated. emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); if (isFirstUpdate || (cmp < 0) || (cmp && !config_->getShowPreviousNextOrders())) { stepDownCount_ = 0; // Redraw entire area in first update } else { int d = calculateStepDistance(tmp.order, tmp.step, curPos_.order, curPos_.step); stepDownCount_ = (d < viewedRowCnt_) ? d : 0; } // If stepChanged is false, repaint all pattern foreChanged_ = true; textChanged_ = true; backChanged_ = true; repaint(); } JamKey PatternEditorPanel::getJamKeyFromLayoutMapping(Qt::Key key) { Configuration::KeyboardLayout selectedLayout = config_->getNoteEntryLayout(); if (config_->mappingLayouts.find (selectedLayout) != config_->mappingLayouts.end()) { std::unordered_map selectedLayoutMapping = config_->mappingLayouts.at (selectedLayout); auto it = std::find_if(selectedLayoutMapping.begin(), selectedLayoutMapping.end(), [key](const std::pair& t) -> bool { return (QKeySequence(key).matches(QKeySequence(QString::fromStdString(t.first))) == QKeySequence::ExactMatch); }); if (it != selectedLayoutMapping.end()) { return (*it).second; } else { throw std::invalid_argument("Unmapped key"); } //something has gone wrong, current layout has no layout map //TODO: handle cleanly? } else throw std::out_of_range("Unmapped Layout"); } bool PatternEditorPanel::enterToneData(QKeyEvent* event) { QString seq = QKeySequence(static_cast(event->modifiers()) | event->key()).toString(); if (seq == QKeySequence( QString::fromUtf8(config_->getKeyOffKey().c_str(), static_cast(config_->getKeyOffKey().length()))).toString()) { bt_->setStepKeyOff(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new SetKeyOffToStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); return true; } else if (seq == QKeySequence( QString::fromUtf8(config_->getEchoBufferKey().c_str(), static_cast(config_->getEchoBufferKey().length()))).toString()) { int n = bt_->getCurrentOctave(); if (n > 3) n = 3; bt_->setEchoBufferAccess(curSongNum_, curPos_.track, curPos_.order, curPos_.step, n); comStack_.lock()->push(new SetEchoBufferAccessQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); return true; } int baseOct = bt_->getCurrentOctave(); if (event->modifiers().testFlag(Qt::NoModifier)) { Qt::Key qtKey = static_cast (event->key()); try { JamKey possibleJamKey = getJamKeyFromLayoutMapping (qtKey); int octaveOffset = 0; switch (possibleJamKey) { case JamKey::HighD2: case JamKey::HighCS2: case JamKey::HighC2: octaveOffset = 2; break; case JamKey::HighB: case JamKey::HighAS: case JamKey::HighA: case JamKey::HighGS: case JamKey::HighG: case JamKey::HighFS: case JamKey::HighF: case JamKey::HighE: case JamKey::HighDS: case JamKey::HighD: case JamKey::HighCS: case JamKey::HighC: case JamKey::LowD2: case JamKey::LowCS2: case JamKey::LowC2: octaveOffset = 1; break; default: break; } setStepKeyOn (JamManager::jamKeyToNote (possibleJamKey), baseOct + octaveOffset); } catch (std::invalid_argument &) {} } return false; } void PatternEditorPanel::setStepKeyOn(Note note, int octave) { if (octave < 8) { bt_->setStepNote(curSongNum_, curPos_.track, curPos_.order, curPos_.step, octave, note, config_->getAutosetInstrument()); comStack_.lock()->push(new SetKeyOnToStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); } } bool PatternEditorPanel::enterInstrumentData(int key) { switch (key) { case Qt::Key_0: setStepInstrument(0x0); return true; case Qt::Key_1: setStepInstrument(0x1); return true; case Qt::Key_2: setStepInstrument(0x2); return true; case Qt::Key_3: setStepInstrument(0x3); return true; case Qt::Key_4: setStepInstrument(0x4); return true; case Qt::Key_5: setStepInstrument(0x5); return true; case Qt::Key_6: setStepInstrument(0x6); return true; case Qt::Key_7: setStepInstrument(0x7); return true; case Qt::Key_8: setStepInstrument(0x8); return true; case Qt::Key_9: setStepInstrument(0x9); return true; case Qt::Key_A: setStepInstrument(0xa); return true; case Qt::Key_B: setStepInstrument(0xb); return true; case Qt::Key_C: setStepInstrument(0xc); return true; case Qt::Key_D: setStepInstrument(0xd); return true; case Qt::Key_E: setStepInstrument(0xe); return true; case Qt::Key_F: setStepInstrument(0xf); return true; default: return false; } } void PatternEditorPanel::setStepInstrument(int num) { bt_->setStepInstrumentDigit(curSongNum_, curPos_.track, curPos_.order, curPos_.step, num, (entryCnt_ == 1)); comStack_.lock()->push(new SetInstrumentToStepQtCommand(this, curPos_, (entryCnt_ == 1))); emit instrumentEntered( bt_->getStepInstrument(curSongNum_, curPos_.track, curPos_.order, curPos_.step)); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) moveCursorToDown(editableStepCnt_); } bool PatternEditorPanel::enterVolumeData(int key) { switch (key) { case Qt::Key_0: setStepVolume(0x0); return true; case Qt::Key_1: setStepVolume(0x1); return true; case Qt::Key_2: setStepVolume(0x2); return true; case Qt::Key_3: setStepVolume(0x3); return true; case Qt::Key_4: setStepVolume(0x4); return true; case Qt::Key_5: setStepVolume(0x5); return true; case Qt::Key_6: setStepVolume(0x6); return true; case Qt::Key_7: setStepVolume(0x7); return true; case Qt::Key_8: setStepVolume(0x8); return true; case Qt::Key_9: setStepVolume(0x9); return true; case Qt::Key_A: setStepVolume(0xa); return true; case Qt::Key_B: setStepVolume(0xb); return true; case Qt::Key_C: setStepVolume(0xc); return true; case Qt::Key_D: setStepVolume(0xd); return true; case Qt::Key_E: setStepVolume(0xe); return true; case Qt::Key_F: setStepVolume(0xf); return true; default: return false; } } void PatternEditorPanel::setStepVolume(int volume) { bool isReversed = (songStyle_.trackAttribs[static_cast(curPos_.track)].source == SoundSource::FM && config_->getReverseFMVolumeOrder()); bt_->setStepVolumeDigit(curSongNum_, curPos_.track, curPos_.order, curPos_.step, volume, isReversed, (entryCnt_ == 1)); comStack_.lock()->push(new SetVolumeToStepQtCommand(this, curPos_, (entryCnt_ == 1))); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) moveCursorToDown(editableStepCnt_); } bool PatternEditorPanel::enterEffectID(int key) { switch (key) { case Qt::Key_0: setStepEffectID("0"); return true; case Qt::Key_1: setStepEffectID("1"); return true; case Qt::Key_2: setStepEffectID("2"); return true; case Qt::Key_3: setStepEffectID("3"); return true; case Qt::Key_4: setStepEffectID("4"); return true; case Qt::Key_5: setStepEffectID("5"); return true; case Qt::Key_6: setStepEffectID("6"); return true; case Qt::Key_7: setStepEffectID("7"); return true; case Qt::Key_8: setStepEffectID("8"); return true; case Qt::Key_9: setStepEffectID("9"); return true; case Qt::Key_A: setStepEffectID("A"); return true; case Qt::Key_B: setStepEffectID("B"); return true; case Qt::Key_C: setStepEffectID("C"); return true; case Qt::Key_D: setStepEffectID("D"); return true; case Qt::Key_E: setStepEffectID("E"); return true; case Qt::Key_F: setStepEffectID("F"); return true; case Qt::Key_G: setStepEffectID("G"); return true; case Qt::Key_H: setStepEffectID("H"); return true; case Qt::Key_I: setStepEffectID("I"); return true; case Qt::Key_J: setStepEffectID("J"); return true; case Qt::Key_K: setStepEffectID("K"); return true; case Qt::Key_L: setStepEffectID("L"); return true; case Qt::Key_M: setStepEffectID("M"); return true; case Qt::Key_N: setStepEffectID("N"); return true; case Qt::Key_O: setStepEffectID("O"); return true; case Qt::Key_P: setStepEffectID("P"); return true; case Qt::Key_Q: setStepEffectID("Q"); return true; case Qt::Key_R: setStepEffectID("R"); return true; case Qt::Key_S: setStepEffectID("S"); return true; case Qt::Key_T: setStepEffectID("T"); return true; case Qt::Key_U: setStepEffectID("U"); return true; case Qt::Key_V: setStepEffectID("V"); return true; case Qt::Key_W: setStepEffectID("W"); return true; case Qt::Key_X: setStepEffectID("X"); return true; case Qt::Key_Y: setStepEffectID("Y"); return true; case Qt::Key_Z: setStepEffectID("Z"); return true; default: return false; } } void PatternEditorPanel::setStepEffectID(QString str) { bt_->setStepEffectIDCharacter(curSongNum_, curPos_.track, curPos_.order, curPos_.step, (curPos_.colInTrack - 3) / 2, str.toStdString(), config_->getFill00ToEffectValue(), (entryCnt_ == 1)); comStack_.lock()->push(new SetEffectIDToStepQtCommand(this, curPos_, (entryCnt_ == 1))); PatternPosition editPos = curPos_; if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) { if (config_->getMoveCursorToRight()) moveCursorToRight(1); else moveCursorToDown(editableStepCnt_); } // Send effect description QString effDetail = tr("Invalid effect"); std::string id = bt_->getStepEffectID(curSongNum_, editPos.track, editPos.order, editPos.step, (editPos.colInTrack - 3) / 2); SoundSource src = songStyle_.trackAttribs.at(static_cast(curPos_.track)).source; emit effectEntered(EffectDescription::getEffectFormatAndDetailString(Effect::toEffectType(src, id))); } bool PatternEditorPanel::enterEffectValue(int key) { switch (key) { case Qt::Key_0: setStepEffectValue(0x0); return true; case Qt::Key_1: setStepEffectValue(0x1); return true; case Qt::Key_2: setStepEffectValue(0x2); return true; case Qt::Key_3: setStepEffectValue(0x3); return true; case Qt::Key_4: setStepEffectValue(0x4); return true; case Qt::Key_5: setStepEffectValue(0x5); return true; case Qt::Key_6: setStepEffectValue(0x6); return true; case Qt::Key_7: setStepEffectValue(0x7); return true; case Qt::Key_8: setStepEffectValue(0x8); return true; case Qt::Key_9: setStepEffectValue(0x9); return true; case Qt::Key_A: setStepEffectValue(0xa); return true; case Qt::Key_B: setStepEffectValue(0xb); return true; case Qt::Key_C: setStepEffectValue(0xc); return true; case Qt::Key_D: setStepEffectValue(0xd); return true; case Qt::Key_E: setStepEffectValue(0xe); return true; case Qt::Key_F: setStepEffectValue(0xf); return true; default: return false; } } void PatternEditorPanel::setStepEffectValue(int value) { int n = (curPos_.colInTrack - 4) / 2; EffectDisplayControl ctrl = EffectDisplayControl::Unset; SoundSource src = songStyle_.trackAttribs[static_cast(curPos_.track)].source; switch (Effect::toEffectType( src, bt_->getStepEffectID(curSongNum_, curPos_.track, curPos_.order, curPos_.step, n))) { case EffectType::VolumeDelay: if (src == SoundSource::FM && config_->getReverseFMVolumeOrder()) ctrl = EffectDisplayControl::ReverseFMVolumeDelay; break; case EffectType::Brightness: if (config_->getReverseFMVolumeOrder()) ctrl = EffectDisplayControl::ReverseFMBrightness; break; default: break; } bt_->setStepEffectValueDigit(curSongNum_, curPos_.track, curPos_.order, curPos_.step, n, value, ctrl, (entryCnt_ == 1)); comStack_.lock()->push(new SetEffectValueToStepQtCommand(this, curPos_, (entryCnt_ == 1))); if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) { if (config_->getMoveCursorToRight()) moveCursorToRight(1); else moveCursorToDown(editableStepCnt_); } } void PatternEditorPanel::insertStep() { bt_->insertStep(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new InsertStepQtCommand(this)); } void PatternEditorPanel::deletePreviousStep() { if (curPos_.step) { bt_->deletePreviousStep(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new DeletePreviousStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } } void PatternEditorPanel::copySelectedCells() { if (selLeftAbovePos_.order == -1) return; int w = 1 + calculateColumnDistance(selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selRightBelowPos_.track, selRightBelowPos_.colInTrack, true); int h = 1 + calculateStepDistance(selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.order, selRightBelowPos_.step); QString str = QString("PATTERN_COPY:%1,%2,%3,") .arg(QString::number(selLeftAbovePos_.colInTrack), QString::number(w), QString::number(h)); PatternPosition pos = selLeftAbovePos_; while (true) { switch (pos.colInTrack) { case 0: str += QString::number(bt_->getStepNoteNumber(curSongNum_, pos.track, pos.order, pos.step)); break; case 1: str += QString::number(bt_->getStepInstrument(curSongNum_, pos.track, pos.order, pos.step)); break; case 2: str += QString::number(bt_->getStepVolume(curSongNum_, pos.track, pos.order, pos.step)); break; case 3: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.track, pos.order, pos.step, 0)); break; case 4: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.track, pos.order, pos.step, 0)); break; case 5: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.track, pos.order, pos.step, 1)); break; case 6: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.track, pos.order, pos.step, 1)); break; case 7: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.track, pos.order, pos.step, 2)); break; case 8: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.track, pos.order, pos.step, 2)); break; case 9: str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, pos.track, pos.order, pos.step, 3)); break; case 10: str += QString::number(bt_->getStepEffectValue(curSongNum_, pos.track, pos.order, pos.step, 3)); break; } if (pos.track == selRightBelowPos_.track && pos.colInTrack == selRightBelowPos_.colInTrack) { if (pos.order == selRightBelowPos_.order && pos.step == selRightBelowPos_.step) { break; } else { pos.setCols(selLeftAbovePos_.track, selLeftAbovePos_.colInTrack); ++pos.step; } } else { ++pos.colInTrack; pos.track += (pos.colInTrack / 11); pos.colInTrack %= 11; } str += ","; } QApplication::clipboard()->setText(str); } void PatternEditorPanel::eraseSelectedCells() { bt_->erasePatternCells(curSongNum_, selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.track, selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new EraseCellsInPatternQtCommand(this)); } void PatternEditorPanel::pasteCopiedCells(const PatternPosition& startPos) { int sCol = 0; std::vector> cells = instantiateCellsFromString(QApplication::clipboard()->text(), sCol); if (sCol > 2 && !((curPos_.colInTrack - sCol) % 2) && static_cast(cells.front().size()) <= 11 - curPos_.colInTrack) sCol = curPos_.colInTrack; bt_->pastePatternCells(curSongNum_, startPos.track, sCol, startPos.order, startPos.step, std::move(cells)); comStack_.lock()->push(new PasteCopiedDataToPatternQtCommand(this)); } void PatternEditorPanel::pasteMixCopiedCells(const PatternPosition& startPos) { int sCol = 0; std::vector> cells = instantiateCellsFromString(QApplication::clipboard()->text(), sCol); if (sCol > 2 && !((curPos_.colInTrack - sCol) % 2) && static_cast(cells.front().size()) <= 11 - curPos_.colInTrack) sCol = curPos_.colInTrack; bt_->pasteMixPatternCells(curSongNum_, startPos.track, sCol, startPos.order, startPos.step, std::move(cells)); comStack_.lock()->push(new PasteMixCopiedDataToPatternQtCommand(this)); } void PatternEditorPanel::pasteOverwriteCopiedCells(const PatternPosition& startPos) { int sCol = 0; std::vector> cells = instantiateCellsFromString(QApplication::clipboard()->text(), sCol); if (sCol > 2 && !((curPos_.colInTrack - sCol) % 2) && static_cast(cells.front().size()) <= 11 - curPos_.colInTrack) sCol = curPos_.colInTrack; bt_->pasteOverwritePatternCells(curSongNum_, startPos.track, sCol, startPos.order, startPos.step, std::move(cells)); comStack_.lock()->push(new PasteOverwriteCopiedDataToPatternQtCommand(this)); } std::vector> PatternEditorPanel::instantiateCellsFromString(QString str, int& startCol) { str.remove(QRegularExpression("PATTERN_(COPY|CUT):")); QString hdRe = "^([0-9]+),([0-9]+),([0-9]+),"; QRegularExpression re(hdRe); QRegularExpressionMatch match = re.match(str); startCol = match.captured(1).toInt(); int w = match.captured(2).toInt(); size_t h = match.captured(3).toUInt(); str.remove(re); std::vector> cells; re = QRegularExpression("^([^,]+),"); for (size_t i = 0; i < h; ++i) { cells.emplace_back(); for (int j = 0; j < w; ++j) { match = re.match(str); if (match.hasMatch()) { cells.at(i).push_back(match.captured(1).toStdString()); str.remove(re); } else { cells.at(i).push_back(str.toStdString()); break; } } } return cells; } void PatternEditorPanel::cutSelectedCells() { if (selLeftAbovePos_.order == -1) return; copySelectedCells(); eraseSelectedCells(); QString str = QApplication::clipboard()->text(); str.replace("COPY", "CUT"); QApplication::clipboard()->setText(str); } void PatternEditorPanel::increaseNoteKey(const PatternPosition& startPos, const PatternPosition& endPos) { int beginTrack = (startPos.colInTrack == 0) ? startPos.track : startPos.track + 1; if (beginTrack <= endPos.track) { bt_->increaseNoteKeyInPattern(curSongNum_, beginTrack, startPos.order, startPos.step, endPos.track, endPos.step); comStack_.lock()->push(new IncreaseNoteKeyInPatternQtCommand(this)); } } void PatternEditorPanel::decreaseNoteKey(const PatternPosition& startPos, const PatternPosition& endPos) { int beginTrack = (startPos.colInTrack == 0) ? startPos.track : startPos.track + 1; if (beginTrack <= endPos.track) { bt_->decreaseNoteKeyInPattern(curSongNum_, beginTrack, startPos.order, startPos.step, endPos.track, endPos.step); comStack_.lock()->push(new DecreaseNoteKeyInPatternQtCommand(this)); } } void PatternEditorPanel::increaseNoteOctave(const PatternPosition& startPos, const PatternPosition& endPos) { int beginTrack = (startPos.colInTrack == 0) ? startPos.track : startPos.track + 1; if (beginTrack <= endPos.track) { bt_->increaseNoteOctaveInPattern(curSongNum_, beginTrack, startPos.order, startPos.step, endPos.track, endPos.step); comStack_.lock()->push(new IncreaseNoteOctaveInPatternQtCommand(this)); } } void PatternEditorPanel::decreaseNoteOctave(const PatternPosition& startPos, const PatternPosition& endPos) { int beginTrack = (startPos.colInTrack == 0) ? startPos.track : startPos.track + 1; if (beginTrack <= endPos.track) { bt_->decreaseNoteOctaveInPattern(curSongNum_, beginTrack, startPos.order, startPos.step, endPos.track, endPos.step); comStack_.lock()->push(new DecreaseNoteOctaveInPatternQtCommand(this)); } } void PatternEditorPanel::setSelectedRectangle(const PatternPosition& start, const PatternPosition& end) { int patMax = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, end.order)) - 1; if (start.compareCols(end) > 0) { if (start.step > end.step) { selLeftAbovePos_ = end; selRightBelowPos_ = { start.track, start.colInTrack, end.order, (start.step > patMax) ? patMax : start.step }; } else { selLeftAbovePos_ = { end.track, end.colInTrack, end.order, start.step }; selRightBelowPos_ = { start.track, start.colInTrack, end.order, end.step }; } } else { if (start.step > end.step) { selLeftAbovePos_ = { start.track, start.colInTrack, end.order, end.step }; selRightBelowPos_ = { end.track, end.colInTrack, end.order, (start.step > patMax) ? patMax : start.step }; } else { selLeftAbovePos_ = { start.track, start.colInTrack, end.order, start.step }; selRightBelowPos_ = end; } } emit selected(true); backChanged_ = true; repaint(); } bool PatternEditorPanel::isSelectedCell(int trackNum, int colNum, int orderNum, int stepNum) { PatternPosition pos{ trackNum, colNum, orderNum, stepNum }; return (selLeftAbovePos_.compareCols(pos) <= 0 && selRightBelowPos_.compareCols(pos) >= 0 && selLeftAbovePos_.compareRows(pos) <= 0 && selRightBelowPos_.compareRows(pos) >= 0); } void PatternEditorPanel::showPatternContextMenu(const PatternPosition& pos, const QPoint& point) { QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* undo = menu.addAction(tr("&Undo")); undo->setIcon(QIcon(":/icon/undo")); QObject::connect(undo, &QAction::triggered, this, [&]() { bt_->undo(); comStack_.lock()->undo(); }); QAction* redo = menu.addAction(tr("&Redo")); redo->setIcon(QIcon(":/icon/redo")); QObject::connect(redo, &QAction::triggered, this, [&]() { bt_->redo(); comStack_.lock()->redo(); }); menu.addSeparator(); QAction* copy = menu.addAction(tr("&Copy")); copy->setIcon(QIcon(":/icon/copy")); QObject::connect(copy, &QAction::triggered, this, &PatternEditorPanel::copySelectedCells); QAction* cut = menu.addAction(tr("Cu&t")); cut->setIcon(QIcon(":/icon/cut")); QObject::connect(cut, &QAction::triggered, this, &PatternEditorPanel::cutSelectedCells); QAction* paste = menu.addAction(tr("&Paste")); paste->setIcon(QIcon(":/icon/paste")); QObject::connect(paste, &QAction::triggered, this, [&]() { pasteCopiedCells(pos); }); auto pasteSp = new QMenu(tr("Paste Specia&l")); menu.addMenu(pasteSp); QAction* pasteMix = pasteSp->addAction(tr("&Mix")); QObject::connect(pasteMix, &QAction::triggered, this, [&]() { pasteMixCopiedCells(pos); }); QAction* pasteOver = pasteSp->addAction(tr("&Overwrite")); QObject::connect(pasteOver, &QAction::triggered, this, [&]() { pasteOverwriteCopiedCells(pos); }); QAction* erase = menu.addAction(tr("&Erase")); QObject::connect(erase, &QAction::triggered, this, &PatternEditorPanel::eraseSelectedCells); QAction* select = menu.addAction(tr("Select &All")); QObject::connect(select, &QAction::triggered, this, [&]() { onSelectPressed(1); }); menu.addSeparator(); auto pattern = new QMenu(tr("Patter&n")); menu.addMenu(pattern); QAction* interpolate = pattern->addAction(tr("&Interpolate")); QObject::connect(interpolate, &QAction::triggered, this, &PatternEditorPanel::onInterpolatePressed); QAction* reverse = pattern->addAction(tr("&Reverse")); QObject::connect(reverse, &QAction::triggered, this, &PatternEditorPanel::onReversePressed); QAction* replace = pattern->addAction(tr("R&eplace Instrument")); QObject::connect(replace, &QAction::triggered, this, &PatternEditorPanel::onReplaceInstrumentPressed); pattern->addSeparator(); QAction* expand = pattern->addAction(tr("E&xpand")); QObject::connect(expand, &QAction::triggered, this, &PatternEditorPanel::onExpandPressed); QAction* shrink = pattern->addAction(tr("S&hrink")); QObject::connect(shrink, &QAction::triggered, this, &PatternEditorPanel::onShrinkPressed); pattern->addSeparator(); auto transpose = new QMenu(tr("&Transpose")); pattern->addMenu(transpose); QAction* deNote = transpose->addAction(tr("&Decrease Note")); QObject::connect(deNote, &QAction::triggered, this, [&]() { onTransposePressed(false, false); }); QAction* inNote = transpose->addAction(tr("&Increase Note")); QObject::connect(inNote, &QAction::triggered, this, [&]() { onTransposePressed(false, true); }); QAction* deOct = transpose->addAction(tr("D&ecrease Octave")); QObject::connect(deOct, &QAction::triggered, this, [&]() { onTransposePressed(true, false); }); QAction* inOct = transpose->addAction(tr("I&ncrease Octave")); QObject::connect(inOct, &QAction::triggered, this, [&]() { onTransposePressed(true, true); }); menu.addSeparator(); QAction* toggle = menu.addAction(tr("To&ggle Track")); QObject::connect(toggle, &QAction::triggered, this, [&] { onToggleTrackPressed(pos.track); }); QAction* solo = menu.addAction(tr("&Solo Track")); QObject::connect(solo, &QAction::triggered, this, [&] { onSoloTrackPressed(pos.track); }); menu.addSeparator(); QAction* exeff = menu.addAction(tr("Expand E&ffect Column")); QObject::connect(exeff, &QAction::triggered, this, [&] { onExpandEffectColumnPressed(pos.track); }); QAction* sheff = menu.addAction(tr("Shrin&k Effect Column")); QObject::connect(sheff, &QAction::triggered, this, [&] { onShrinkEffectColumnPressed(pos.track); }); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) undo->setShortcutVisibleInContextMenu(true); redo->setShortcutVisibleInContextMenu(true); copy->setShortcutVisibleInContextMenu(true); cut->setShortcutVisibleInContextMenu(true); paste->setShortcutVisibleInContextMenu(true); pasteMix->setShortcutVisibleInContextMenu(true); erase->setShortcutVisibleInContextMenu(true); select->setShortcutVisibleInContextMenu(true); interpolate->setShortcutVisibleInContextMenu(true); reverse->setShortcutVisibleInContextMenu(true); replace->setShortcutVisibleInContextMenu(true); deNote->setShortcutVisibleInContextMenu(true); inNote->setShortcutVisibleInContextMenu(true); deOct->setShortcutVisibleInContextMenu(true); inOct->setShortcutVisibleInContextMenu(true); toggle->setShortcutVisibleInContextMenu(true); solo->setShortcutVisibleInContextMenu(true); exeff->setShortcutVisibleInContextMenu(true); sheff->setShortcutVisibleInContextMenu(true); #endif undo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z)); redo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Y)); undo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z)); copy->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C)); cut->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X)); paste->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V)); pasteMix->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); erase->setShortcut(QKeySequence(Qt::Key_Delete)); select->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); interpolate->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_G)); reverse->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); replace->setShortcut(QKeySequence(Qt::ALT + Qt::Key_S)); deNote->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F1)); inNote->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F2)); deOct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F3)); inOct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4)); toggle->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F9)); solo->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F10)); exeff->setShortcut(QKeySequence(Qt::ALT + Qt::Key_L)); sheff->setShortcut(QKeySequence(Qt::ALT + Qt::Key_K)); if (bt_->isJamMode() || pos.order < 0 || pos.track < 0) { copy->setEnabled(false); cut->setEnabled(false); paste->setEnabled(false); pasteMix->setEnabled(false); pasteOver->setEnabled(false); erase->setEnabled(false); interpolate->setEnabled(false); reverse->setEnabled(false); replace->setEnabled(false); expand->setEnabled(false); shrink->setEnabled(false); deNote->setEnabled(false); inNote->setEnabled(false); deOct->setEnabled(false); inOct->setEnabled(false); } else { QString clipText = QApplication::clipboard()->text(); if (!clipText.startsWith("PATTERN_COPY") && !clipText.startsWith("PATTERN_CUT")) { paste->setEnabled(false); pasteMix->setEnabled(false); pasteOver->setEnabled(false); } if (selRightBelowPos_.order < 0 || !isSelectedCell(pos.track, pos.colInTrack, pos.order, pos.step)) { copy->setEnabled(false); cut->setEnabled(false); erase->setEnabled(false); interpolate->setEnabled(false); reverse->setEnabled(false); replace->setEnabled(false); expand->setEnabled(false); shrink->setEnabled(false); deNote->setEnabled(false); inNote->setEnabled(false); deOct->setEnabled(false); inOct->setEnabled(false); } } if (!comStack_.lock()->canUndo()) { undo->setEnabled(false); } if (!comStack_.lock()->canRedo()) { redo->setEnabled(false); } if (pos.track < 0) { toggle->setEnabled(false); solo->setEnabled(false); exeff->setEnabled(false); sheff->setEnabled(false); } menu.exec(mapToGlobal(point)); } /********** Slots **********/ void PatternEditorPanel::onHScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (config_->getMoveCursorByHorizontalScroll()) { if (int dif = num - calculateColNumInRow(curPos_.track, curPos_.colInTrack)) moveCursorToRight(dif); } else { if (int dif = num - leftTrackNum_) moveViewToRight(dif); } } void PatternEditorPanel::onVScrollBarChanged(int num) { Ui::EventGuard eg(isIgnoreToSlider_); // Skip if position has already changed in panel if (int dif = num - curPos_.step) moveCursorToDown(dif); } void PatternEditorPanel::setCurrentTrack(int num) { Ui::EventGuard eg(isIgnoreToOrder_); int dif = calculateColumnDistance(curPos_.track, curPos_.colInTrack, num, 0); moveCursorToRight(dif); } void PatternEditorPanel::setCurrentOrder(int num) { Ui::EventGuard eg(isIgnoreToOrder_); int step = std::min(curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, num)) - 1); int dif = calculateStepDistance(curPos_.order, curPos_.step, num, step); moveCursorToDown(dif); } void PatternEditorPanel::onOrderListEdited() { // Reset position memory hovPos_ = { -1, -1, -1, -1 }; mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; shiftPressedPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); redrawByPatternChanged(true); } void PatternEditorPanel::onDefaultPatternSizeChanged() { // Check pattern size int end = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); if (curPos_.step >= end) curPos_.step = end - 1; redrawByPatternChanged(true); } void PatternEditorPanel::setPatternHighlight1Count(int count) { hl1Cnt_ = count; backChanged_ = true; textChanged_ = true; repaint(); } void PatternEditorPanel::setPatternHighlight2Count(int count) { hl2Cnt_ = count; backChanged_ = true; textChanged_ = true; repaint(); } void PatternEditorPanel::setEditableStep(int n) { editableStepCnt_ = n; } void PatternEditorPanel::onSongLoaded() { // Initialize cursor position curSongNum_ = bt_->getCurrentSongNumber(); curPos_ = { bt_->getCurrentTrackAttribute().number, 0, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber() }; songStyle_ = bt_->getSongStyle(curSongNum_); size_t trackCnt = songStyle_.trackAttribs.size(); rightEffn_ = std::vector(trackCnt); std::generate(rightEffn_.begin(), rightEffn_.end(), [&, i = 0]() mutable { return static_cast(bt_->getEffectDisplayWidth(curSongNum_, i++)); }); leftTrackNum_ = 0; updateTracksWidthFromLeftToEnd(); hovPos_ = { -1, -1, -1, -1 }; mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; shiftPressedPos_ = { -1, -1, -1, -1 }; entryCnt_ = 0; selectAllState_ = -1; emit selected(false); redrawAll(); } void PatternEditorPanel::onDeletePressed() { if (bt_->isJamMode()) return; if (selLeftAbovePos_.order != -1) { // Delete region eraseSelectedCells(); } else { switch (curPos_.colInTrack) { case 0: bt_->eraseStepNote(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new EraseStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); break; case 1: bt_->eraseStepInstrument(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new EraseInstrumentInStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); break; case 2: bt_->eraseStepVolume(curSongNum_, curPos_.track, curPos_.order, curPos_.step); comStack_.lock()->push(new EraseVolumeInStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); break; case 3: case 5: case 7: case 9: bt_->eraseStepEffect(curSongNum_, curPos_.track, curPos_.order, curPos_.step, (curPos_.colInTrack - 3) / 2); comStack_.lock()->push(new EraseEffectInStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); break; case 4: case 6: case 8: case 10: bt_->eraseStepEffectValue(curSongNum_, curPos_.track, curPos_.order, curPos_.step, (curPos_.colInTrack - 4) / 2); comStack_.lock()->push(new EraseEffectValueInStepQtCommand(this)); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_); break; } } } void PatternEditorPanel::onPastePressed() { if (!bt_->isJamMode()) pasteCopiedCells(curPos_); } void PatternEditorPanel::onPasteMixPressed() { if (!bt_->isJamMode()) pasteMixCopiedCells(curPos_); } void PatternEditorPanel::onPasteOverwritePressed() { if (!bt_->isJamMode()) pasteOverwriteCopiedCells(curPos_); } void PatternEditorPanel::onSelectPressed(int type) { switch (type) { case 0: // None { selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); backChanged_ = true; repaint(); break; } case 1: // All { selectAllState_ = (selectAllState_ + 1) % 2; int max = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1; if (!selectAllState_) { PatternPosition start = { curPos_.track, 0, curPos_.order, 0 }; PatternPosition end = { curPos_.track, 10, curPos_.order, max }; setSelectedRectangle(start, end); } else { PatternPosition start = { 0, 0, curPos_.order, 0 }; PatternPosition end = { static_cast(songStyle_.trackAttribs.size() - 1), 10, curPos_.order, max }; setSelectedRectangle(start, end); } break; } case 2: // Row { selectAllState_ = -1; PatternPosition start = { 0, 0, curPos_.order, curPos_.step }; PatternPosition end = { static_cast(songStyle_.trackAttribs.size() - 1), 10, curPos_.order, curPos_.step }; setSelectedRectangle(start, end); break; } case 3: // Column { selectAllState_ = -1; PatternPosition start = { curPos_.track, curPos_.colInTrack, curPos_.order, 0 }; PatternPosition end = { curPos_.track, curPos_.colInTrack, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } case 4: // Pattern { selectAllState_ = -1; PatternPosition start = { curPos_.track, 0, curPos_.order, 0 }; PatternPosition end = { curPos_.track, 10, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } case 5: // Order { selectAllState_ = -1; PatternPosition start = { 0, 0, curPos_.order, 0 }; PatternPosition end = { static_cast(songStyle_.trackAttribs.size() - 1), 10, curPos_.order, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1) }; setSelectedRectangle(start, end); break; } } } void PatternEditorPanel::onTransposePressed(bool isOctave, bool isIncreased) { if (bt_->isJamMode()) return; if (isOctave) { if (isIncreased) { if (selLeftAbovePos_.order != -1) increaseNoteOctave(selLeftAbovePos_, selRightBelowPos_); else increaseNoteOctave(curPos_, curPos_); } else { if (selLeftAbovePos_.order != -1) decreaseNoteOctave(selLeftAbovePos_, selRightBelowPos_); else decreaseNoteOctave(curPos_, curPos_); } } else { if (isIncreased) { if (selLeftAbovePos_.order != -1) increaseNoteKey(selLeftAbovePos_, selRightBelowPos_); else increaseNoteKey(curPos_, curPos_); } else { if (selLeftAbovePos_.order != -1) decreaseNoteKey(selLeftAbovePos_, selRightBelowPos_); else decreaseNoteKey(curPos_, curPos_); } } } void PatternEditorPanel::onToggleTrackPressed(int track) { bt_->setTrackMuteState(track, !bt_->isMute(track)); isMuteElse_ = false; redrawByMaskChanged(); } void PatternEditorPanel::onSoloTrackPressed(int track) { int trackCnt = static_cast(songStyle_.trackAttribs.size()); if (isMuteElse_) { if (bt_->isMute(track)) { for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, (t == track) ? false : true); } else { isMuteElse_ = false; for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, false); } } else { isMuteElse_ = true; for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, (t == track) ? false : isMuteElse_); } redrawByMaskChanged(); } void PatternEditorPanel::onUnmuteAllPressed() { int trackCnt = static_cast(songStyle_.trackAttribs.size()); for (int t = 0; t < trackCnt; ++t) bt_->setTrackMuteState(t, false); isMuteElse_ = false; redrawByMaskChanged(); } void PatternEditorPanel::onExpandPressed() { if (selLeftAbovePos_.order == -1) return; bt_->expandPattern(curSongNum_, selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.track, selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ExpandPatternQtCommand(this)); } void PatternEditorPanel::onShrinkPressed() { if (selLeftAbovePos_.order == -1) return; bt_->shrinkPattern(curSongNum_, selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.track, selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ShrinkPatternQtCommand(this)); } void PatternEditorPanel::onInterpolatePressed() { if (selLeftAbovePos_.order == -1) return; bt_->interpolatePattern(curSongNum_, selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.track, selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new InterpolatePatternQtCommand(this)); } void PatternEditorPanel::onReversePressed() { if (selLeftAbovePos_.order == -1) return; bt_->reversePattern(curSongNum_, selLeftAbovePos_.track, selLeftAbovePos_.colInTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, selRightBelowPos_.track, selRightBelowPos_.colInTrack, selRightBelowPos_.step); comStack_.lock()->push(new ReversePatternQtCommand(this)); } void PatternEditorPanel::onReplaceInstrumentPressed() { if (selLeftAbovePos_.order == -1) return; int curInst = bt_->getCurrentInstrumentNumber(); if (curInst == -1) return; int beginTrack = (selLeftAbovePos_.colInTrack < 2) ? selLeftAbovePos_.track : (selLeftAbovePos_.track + 1); int endTrack = (selRightBelowPos_.colInTrack == 0) ? (selRightBelowPos_.track - 1) : selRightBelowPos_.track; if (beginTrack <= endTrack) { bt_->replaceInstrumentInPattern(curSongNum_, beginTrack, selLeftAbovePos_.order, selLeftAbovePos_.step, endTrack, selRightBelowPos_.step, curInst); comStack_.lock()->push(new ReplaceInstrumentInPatternQtCommand(this)); } } void PatternEditorPanel::onExpandEffectColumnPressed(int trackNum) { size_t tn = static_cast(trackNum); if (rightEffn_.at(tn) == 3) return; bt_->setEffectDisplayWidth(curSongNum_, trackNum, static_cast(++rightEffn_[tn])); updateTracksWidthFromLeftToEnd(); if (config_->getMoveCursorByHorizontalScroll()) { emit effectColsCompanded(calculateColNumInRow(curPos_.track, curPos_.colInTrack), getFullColmunSize()); } else { emit effectColsCompanded(leftTrackNum_, getScrollableCountByTrack()); } redrawAll(); } void PatternEditorPanel::onShrinkEffectColumnPressed(int trackNum) { size_t tn = static_cast(trackNum); if (rightEffn_.at(tn) == 0) return; bt_->setEffectDisplayWidth(curSongNum_, trackNum, static_cast(--rightEffn_[tn])); updateTracksWidthFromLeftToEnd(); if (config_->getMoveCursorByHorizontalScroll()) { emit effectColsCompanded(calculateColNumInRow(curPos_.track, curPos_.colInTrack), getFullColmunSize()); } else { emit effectColsCompanded(leftTrackNum_, getScrollableCountByTrack()); } redrawAll(); } void PatternEditorPanel::onFollowModeChanged() { curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber()); emit vScrollBarChangeRequested( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1); // Force redraw all area followModeChanged_ = true; redrawPatterns(); } /********** Events **********/ bool PatternEditorPanel::event(QEvent *event) { switch (event->type()) { case QEvent::KeyPress: return keyPressed(dynamic_cast(event)); case QEvent::KeyRelease: return keyReleased(dynamic_cast(event)); case QEvent::HoverMove: return mouseHoverd(dynamic_cast(event)); default: return QWidget::event(event); } } bool PatternEditorPanel::keyPressed(QKeyEvent *event) { /* General Keys */ switch (event->key()) { case Qt::Key_Return: emit returnPressed(); return true; case Qt::Key_Shift: shiftPressedPos_ = curPos_; return true; case Qt::Key_Left: moveCursorToRight(-1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Right: moveCursorToRight(1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; case Qt::Key_Up: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { if (event->modifiers().testFlag(Qt::ControlModifier)) { int base; if (curPos_.step) { base = curPos_.step; } else { base = static_cast(bt_->getPatternSizeFromOrderNumber( curSongNum_, (curPos_.order) ? (curPos_.order - 1) : (static_cast(bt_->getOrderSize(curSongNum_)) - 1))); } moveCursorToDown((base - 1) / hl1Cnt_ * hl1Cnt_ - base); } else { moveCursorToDown(editableStepCnt_ ? -editableStepCnt_ : -1); } if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Down: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { if (event->modifiers().testFlag(Qt::ControlModifier)) { int next = (curPos_.step / hl1Cnt_ + 1) * hl1Cnt_; int size = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)); if (next < size) moveCursorToDown(next - curPos_.step); else moveCursorToDown(size - curPos_.step); } else { moveCursorToDown(editableStepCnt_ ? editableStepCnt_ : 1); } if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Tab: if (curPos_.track == static_cast(songStyle_.trackAttribs.size()) - 1) { if (config_->getWarpCursor()) moveCursorToRight(-calculateColNumInRow(curPos_.track, curPos_.colInTrack)); } else { moveCursorToRight(5 + 2 * rightEffn_[static_cast(curPos_.track)] - curPos_.colInTrack); } return true; case Qt::Key_Backtab: if (curPos_.track == 0) { if (config_->getWarpCursor()) moveCursorToRight(getFullColmunSize() - 1); } else { moveCursorToRight(-5 - 2 * rightEffn_[static_cast(curPos_.track) - 1] - curPos_.colInTrack); } return true; case Qt::Key_Home: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-curPos_.step); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_End: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown( static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - curPos_.step - 1); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageUp: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(-static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_PageDown: if (bt_->isPlaySong() && bt_->isFollowPlay()) { return false; } else { moveCursorToDown(static_cast(config_->getPageJumpLength())); if (event->modifiers().testFlag(Qt::ShiftModifier)) setSelectedRectangle(shiftPressedPos_, curPos_); else onSelectPressed(0); return true; } case Qt::Key_Insert: if (bt_->isJamMode()) { return false; } else { insertStep(); return true; } case Qt::Key_Backspace: if (bt_->isJamMode()) { return false; } else { deletePreviousStep(); return true; } case Qt::Key_Menu: { QPoint point = calculateCurrentCursorPosition(); point.setX(point.x() + 24); point.setY(point.y() - 16); showPatternContextMenu(curPos_, point); return true; } default: if (!bt_->isJamMode()) { // Pattern edit if (!config_->getKeyRepetition() && event->isAutoRepeat()) return false; switch (curPos_.colInTrack) { case 0: return enterToneData(event); case 1: if (event->modifiers().testFlag(Qt::NoModifier)) return enterInstrumentData(event->key()); break; case 2: if (event->modifiers().testFlag(Qt::NoModifier)) return enterVolumeData(event->key()); break; case 3: case 5: case 7: case 9: if (event->modifiers().testFlag(Qt::NoModifier)) return enterEffectID(event->key()); break; case 4: case 6: case 8: case 10: if (event->modifiers().testFlag(Qt::NoModifier)) return enterEffectValue(event->key()); break; } } return false; } } bool PatternEditorPanel::keyReleased(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Shift: shiftPressedPos_ = { -1, -1, -1, -1 }; return true; default: return false; } } void PatternEditorPanel::paintEvent(QPaintEvent *event) { if (bt_) { // Check order size int odrSize = static_cast(bt_->getOrderSize(curSongNum_)); if (curPos_.order >= odrSize) curPos_.setRows(odrSize - 1, 0); const QRect& area = event->rect(); if (area.x() == 0 && area.y() == 0) { drawPattern(area); } else { drawPattern(rect()); } } } void PatternEditorPanel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); // Recalculate center row position curRowY_ = (geometry().height() + headerHeight_ - stepFontHeight_) >> 1; curRowBaselineY_ = curRowY_ + stepFontAscent_ - (stepFontLeading_ >> 1); initDisplay(); redrawAll(); } void PatternEditorPanel::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event) setFocus(); mousePressPos_ = hovPos_; doubleClickPos_ = mousePressPos_; mouseReleasePos_ = { -1, -1, -1, -1 }; isPressedPlus_ = false; isPressedMinus_ = false; if (event->button() == Qt::LeftButton) { if (mousePressPos_.order == -2 && mousePressPos_.track >= 0) { int w = calculateTracksWidthWithRowNum(leftTrackNum_, mousePressPos_.track - 1) + hdMuteToggleWidth_ + stepFontWidth_ / 2; if (w < event->pos().x() && event->pos().x() < w + hdEffCompandButtonWidth_ + stepFontWidth_) { if (event->pos().y() < headerHeight_ / 2) isPressedPlus_ = true; else isPressedMinus_ = true; } } selLeftAbovePos_ = { -1, -1, -1, -1 }; selRightBelowPos_ = { -1, -1, -1, -1 }; selectAllState_ = -1; emit selected(false); } } void PatternEditorPanel::mouseMoveEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { if (mousePressPos_.track < 0 || mousePressPos_.order < 0) return; // Start point is out of range if (hovPos_.track >= 0 && hovPos_.order >= 0) { setSelectedRectangle(mousePressPos_, hovPos_); } if (event->x() < stepNumWidth_ && leftTrackNum_ > 0) { if (config_->getMoveCursorByHorizontalScroll()) { moveCursorToRight(-(5 + 2 * rightEffn_.at(static_cast(leftTrackNum_) - 1))); } else { moveViewToRight(-1); } } else if (event->x() > geometry().width() - stepNumWidth_ && hovPos_.track != -1) { if (config_->getMoveCursorByHorizontalScroll()) { moveCursorToRight(5 + 2 * rightEffn_.at(static_cast(leftTrackNum_))); } else { moveViewToRight(1); } } if (event->pos().y() < headerHeight_ + stepFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1); } else if (event->pos().y() > geometry().height() - stepFontHeight_) { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(1); } } } void PatternEditorPanel::mouseReleaseEvent(QMouseEvent* event) { mouseReleasePos_ = hovPos_; switch (event->button()) { case Qt::LeftButton: if (mousePressPos_ == mouseReleasePos_) { // Jump cell if (hovPos_.order >= 0 && hovPos_.step >= 0 && hovPos_.track >= 0 && hovPos_.colInTrack >= 0) { int horDif = calculateColumnDistance(curPos_.track, curPos_.colInTrack, hovPos_.track, hovPos_.colInTrack); int verDif = calculateStepDistance(curPos_.order, curPos_.step, hovPos_.order, hovPos_.step); moveCursorToRight(horDif); if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(verDif); } else if (hovPos_.order == -2 && hovPos_.track >= 0) { // Header if (isPressedPlus_) { onExpandEffectColumnPressed(hovPos_.track); } else if (isPressedMinus_) { onShrinkEffectColumnPressed(hovPos_.track); } else { onToggleTrackPressed(hovPos_.track); int horDif = calculateColumnDistance(curPos_.track, curPos_.colInTrack, hovPos_.track, 0); moveCursorToRight(horDif); } } else if (hovPos_.track == -2 && hovPos_.order >= 0 && hovPos_.step >= 0) { // Step number if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int verDif = calculateStepDistance(curPos_.order, curPos_.step, hovPos_.order, hovPos_.step); moveCursorToDown(verDif); } } } break; case Qt::RightButton: // Show context menu { if (mousePressPos_.order == -2) { // Header QMenu menu; // Leave Before Qt5.7.0 style due to windows xp QAction* toggle = menu.addAction(tr("To&ggle Track")); QObject::connect(toggle, &QAction::triggered, this, [&] { onToggleTrackPressed(mousePressPos_.track); }); QAction* solo = menu.addAction(tr("&Solo Track")); QObject::connect(solo, &QAction::triggered, this, [&] { onSoloTrackPressed(mousePressPos_.track); }); QAction* unmute = menu.addAction(tr("&Unmute All Tracks")); QObject::connect(unmute, &QAction::triggered, this, &PatternEditorPanel::onUnmuteAllPressed); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) toggle->setShortcutVisibleInContextMenu(true); solo->setShortcutVisibleInContextMenu(true); #endif toggle->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F9)); solo->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F10)); if (mousePressPos_.track < 0) { toggle->setEnabled(false); solo->setEnabled(false); unmute->setEnabled(false); } menu.exec(mapToGlobal(event->pos())); } else { // Pattern showPatternContextMenu(mousePressPos_, event->pos()); } break; } case Qt::XButton1: { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int order = curPos_.order - 1; if (order < 0) order = static_cast(bt_->getOrderSize(curSongNum_)) - 1; int step = std::min( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1); int d = calculateStepDistance(curPos_.order, curPos_.step, order, step); moveCursorToDown(d); } break; } case Qt::XButton2: { if (!bt_->isPlaySong() || !bt_->isFollowPlay()) { int order = curPos_.order + 1; if (static_cast(bt_->getOrderSize(curSongNum_)) - 1 < order) order = 0; int step = std::min( curPos_.step, static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1); int d = calculateStepDistance(curPos_.order, curPos_.step, order, step); moveCursorToDown(d); } break; } default: break; } mousePressPos_ = { -1, -1, -1, -1 }; mouseReleasePos_ = { -1, -1, -1, -1 }; } void PatternEditorPanel::mouseDoubleClickEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { if (doubleClickPos_.order >= 0) { if (!config_->getDontSelectOnDoubleClick()) { if (doubleClickPos_.track >= 0) { onSelectPressed(4); return; } else if (doubleClickPos_.track == -2) { onSelectPressed(5); return; } } } else if (doubleClickPos_.order == -2) { if (doubleClickPos_.track >= 0 && !isPressedPlus_ && !isPressedMinus_) { bool flag = true; int trackCnt = static_cast(songStyle_.trackAttribs.size()); for (int t = 0; t < trackCnt; ++t) { if (t != doubleClickPos_.track) flag &= bt_->isMute(t); } if (flag) onUnmuteAllPressed(); else onSoloTrackPressed(doubleClickPos_.track); return; } } } // Else mousePressEvent(event); } bool PatternEditorPanel::mouseHoverd(QHoverEvent *event) { QPoint pos = event->pos(); PatternPosition oldPos = hovPos_; // Detect Step if (pos.y() <= headerHeight_) { // Track header hovPos_.setRows(-2, -2); } else { if (pos.y() < curRowY_) { int tmpOdr = curPos_.order; int tmpStep = curPos_.step + (pos.y() - curRowY_) / stepFontHeight_ - 1; while (true) { if (tmpStep < 0) { if (tmpOdr == 0) { hovPos_.setRows(-1, -1); break; } else { tmpStep += bt_->getPatternSizeFromOrderNumber(curSongNum_, --tmpOdr); } } else { hovPos_.setRows(tmpOdr, tmpStep); break; } } } else { int tmpOdr = curPos_.order; int tmpStep = curPos_.step + (pos.y() - curRowY_) / stepFontHeight_; while (true) { int endStep = static_cast(bt_->getPatternSizeFromOrderNumber(curSongNum_, tmpOdr)); if (tmpStep < endStep) { hovPos_.setRows(tmpOdr, tmpStep); break; } else { if (tmpOdr == static_cast(bt_->getOrderSize(curSongNum_)) - 1) { hovPos_.setRows(-1, -1); break; } else { ++tmpOdr; tmpStep -= endStep; } } } } } // Detect column if (pos.x() <= stepNumWidth_) { // Row number hovPos_.setCols(-2, -2); } else { int tmpWidth = stepNumWidth_; for (int i = leftTrackNum_; ; ) { tmpWidth += (toneNameWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 0); break; } tmpWidth += (instWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 1); break; } tmpWidth += (volWidth_ + widthSpaceDbl_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 2); break; } bool flag = false; for (int j = 0; j <= rightEffn_.at(static_cast(i)); ++j) { tmpWidth += (effIDWidth_ + widthSpace_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 3 + 2 * j); flag = true; break; } tmpWidth += (effValWidth_ + widthSpace_); if (pos.x() <= tmpWidth) { hovPos_.setCols(i, 4 + 2 * j); flag = true; break; } } if (flag) break; ++i; if (i == static_cast(songStyle_.trackAttribs.size())) { hovPos_.setCols(-1, -1); break; } } } if (hovPos_ != oldPos) redrawByHoverChanged(); return true; } void PatternEditorPanel::wheelEvent(QWheelEvent *event) { if (bt_->isPlaySong() && bt_->isFollowPlay()) return; int degree = event->angleDelta().y() / 8; moveCursorToDown(-degree / 15); } void PatternEditorPanel::leaveEvent(QEvent* event) { Q_UNUSED(event) // Clear mouse hover selection hovPos_ = { -1, -1, -1, -1 }; } void PatternEditorPanel::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData) { PatternEditorPanel *self = reinterpret_cast(userData); Q_UNUSED(delay) // Note-On/Note-Off if (len == 3 && (msg[0] & 0xe0) == 0x80) { uint8_t status = msg[0]; uint8_t key = msg[1]; uint8_t velocity = msg[2]; QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_); method.invoke(self, Qt::QueuedConnection, Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity)); } } void PatternEditorPanel::midiKeyEvent(uchar status, uchar key, uchar velocity) { if (!bt_->isJamMode()) { bool release = ((status & 0xf0) == 0x80) || velocity == 0; if (!release) { std::pair octaveAndNote = noteNumberToOctaveAndNote(static_cast(key) - 12); setStepKeyOn(octaveAndNote.second, octaveAndNote.first); } } } BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_editor_panel.hpp000066400000000000000000000203371362177441300275040ustar00rootroot00000000000000#ifndef PATTERN_EDITOR_PANEL_HPP #define PATTERN_EDITOR_PANEL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bamboo_tracker.hpp" #include "configuration.hpp" #include "song.hpp" #include "gui/pattern_editor/pattern_position.hpp" #include "gui/color_palette.hpp" #include "misc.hpp" class PatternEditorPanel : public QWidget { Q_OBJECT public: explicit PatternEditorPanel(QWidget *parent = nullptr); ~PatternEditorPanel() override; void setCore(std::shared_ptr core); bool isReadyCore() const; void setCommandStack(std::weak_ptr stack); void setConfiguration(std::shared_ptr config); void setColorPallete(std::shared_ptr palette); void changeEditable(); int getFullColmunSize() const; void updatePositionByStepUpdate(bool isFirstUpdate); int getScrollableCountByTrack() const; void copySelectedCells(); void cutSelectedCells(); int getCurrentTrack() const; void redrawByPatternChanged(bool patternSizeChanged = false); void redrawByFocusChanged(); void redrawByHoverChanged(); void redrawByMaskChanged(); void redrawPatterns(); void redrawAll(); void resetEntryCount(); void freeze(); void unfreeze(); QString getHeaderFont() const; int getHeaderFontSize() const; QString getRowsFont() const; int getRowsFontSize() const; void setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize); public slots: void onHScrollBarChanged(int num); void onVScrollBarChanged(int num); void setCurrentTrack(int num); void setCurrentOrder(int num); void onOrderListEdited(); void onDefaultPatternSizeChanged(); void setPatternHighlight1Count(int count); void setPatternHighlight2Count(int count); void setEditableStep(int n); void onSongLoaded(); void onDeletePressed(); void onPastePressed(); void onPasteMixPressed(); void onPasteOverwritePressed(); /// 0: None /// 1: All /// 2: Row /// 3: Column /// 4: Pattern /// 5: Order void onSelectPressed(int type); void onTransposePressed(bool isOctave, bool isIncreased); void onToggleTrackPressed(int track); void onSoloTrackPressed(int track); void onUnmuteAllPressed(); void onExpandPressed(); void onShrinkPressed(); void onInterpolatePressed(); void onReversePressed(); void onReplaceInstrumentPressed(); void onExpandEffectColumnPressed(int trackNum); void onShrinkEffectColumnPressed(int trackNum); void onFollowModeChanged(); signals: void hScrollBarChangeRequested(int num); void vScrollBarChangeRequested(int num, int max); void currentTrackChanged(int num); void currentOrderChanged(int num, int max); void effectColsCompanded(int num, int max); void selected(bool isSelected); void returnPressed(); void instrumentEntered(int num); void effectEntered(QString text); protected: virtual bool event(QEvent *event) override; bool keyPressed(QKeyEvent* event); bool keyReleased(QKeyEvent* event); virtual void paintEvent(QPaintEvent* event) override; virtual void resizeEvent(QResizeEvent* event) override; virtual void mousePressEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void mouseDoubleClickEvent(QMouseEvent* event) override; bool mouseHoverd(QHoverEvent* event); virtual void wheelEvent(QWheelEvent* event) override; virtual void leaveEvent(QEvent* event) override; // Midi private: static void midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData); private slots: void midiKeyEvent(uchar status, uchar key, uchar velocity); private: std::unique_ptr completePixmap_, backPixmap_, textPixmap_, forePixmap_, headerPixmap_; std::shared_ptr bt_; std::weak_ptr comStack_; std::shared_ptr config_; std::shared_ptr palette_; QFont stepFont_, headerFont_; int stepFontWidth_, stepFontHeight_, stepFontAscent_, stepFontLeading_; int headerFontAscent_; int widthSpace_, widthSpaceDbl_; int stepNumWidthCnt_, stepNumWidth_, stepNumBase_; int baseTrackWidth_; int toneNameWidth_, instWidth_; int volWidth_; int effWidth_, effIDWidth_, effValWidth_; int tracksWidthFromLeftToEnd_; int hdMuteToggleWidth_, hdEffCompandButtonWidth_; int headerHeight_; int hdPlusY_, hdMinusY_; int curRowBaselineY_; int curRowY_; std::vector rightEffn_; int leftTrackNum_; SongStyle songStyle_; int curSongNum_; PatternPosition curPos_, hovPos_; PatternPosition mousePressPos_, mouseReleasePos_; PatternPosition selLeftAbovePos_, selRightBelowPos_; PatternPosition shiftPressedPos_; PatternPosition doubleClickPos_; bool isIgnoreToSlider_, isIgnoreToOrder_; bool isPressedPlus_, isPressedMinus_; int entryCnt_; int selectAllState_; bool isMuteElse_; int hl1Cnt_, hl2Cnt_; int editableStepCnt_; int viewedRowCnt_; int viewedRegionHeight_; int viewedRowsHeight_, viewedRowOffset_, viewedCenterY_, viewedCenterBaseY_; PatternPosition viewedFirstPos_, viewedCenterPos_, viewedLastPos_; bool backChanged_, textChanged_, foreChanged_, headerChanged_, focusChanged_, followModeChanged_; bool hasFocussedBefore_; int stepDownCount_; bool freezed_; std::atomic_bool repaintable_; // Recurrensive repaint guard std::atomic_int repaintingCnt_; // Meta methods int midiKeyEventMethod_; void updateSizes(); void initDisplay(); void drawPattern(const QRect& rect); void drawRows(int maxWidth, int trackSize); void quickDrawRows(int maxWidth, int trackSize); /// Return: /// track width int drawStep(QPainter& forePainter, QPainter& textPainter, QPainter& backPainter, int trackNum, int orderNum, int stepNum, int x, int baseY, int rowY); void drawHeaders(int maxWidth, int trackSize); void drawBorders(int maxWidth, int trackSize); void drawShadow(); int calculateTracksWidthWithRowNum(int begin, int end) const; int calculateColNumInRow(int trackNum, int colNumInTrack, bool isExpanded = false) const; int calculateColumnDistance(int beginTrack, int beginColumn, int endTrack, int endColumn, bool isExpanded = false) const; int calculateStepDistance(int beginOrder, int beginStep, int endOrder, int endStep) const; PatternPosition calculatePositionFrom(int order, int step, int by) const; QPoint calculateCurrentCursorPosition() const; inline void updateTracksWidthFromLeftToEnd() { tracksWidthFromLeftToEnd_ = calculateTracksWidthWithRowNum( leftTrackNum_, static_cast(songStyle_.trackAttribs.size()) - 1); } void moveCursorToRight(int n); void moveViewToRight(int n); void moveCursorToDown(int n); bool enterToneData(QKeyEvent* event); void setStepKeyOn(Note note, int octave); bool enterInstrumentData(int key); void setStepInstrument(int num); bool enterVolumeData(int key); void setStepVolume(int volume); bool enterEffectID(int key); void setStepEffectID(QString str); bool enterEffectValue(int key); void setStepEffectValue(int value); inline int updateEntryCount() { entryCnt_ = (entryCnt_ + 1) % 2; return entryCnt_; } void insertStep(); void deletePreviousStep(); void eraseSelectedCells(); void pasteCopiedCells(const PatternPosition& startPos); void pasteMixCopiedCells(const PatternPosition& startPos); void pasteOverwriteCopiedCells(const PatternPosition& startPos); std::vector > instantiateCellsFromString(QString str, int& startCol); void increaseNoteKey(const PatternPosition& startPos, const PatternPosition& endPos); void decreaseNoteKey(const PatternPosition& startPos, const PatternPosition& endPos); void increaseNoteOctave(const PatternPosition& startPos, const PatternPosition& endPos); void decreaseNoteOctave(const PatternPosition& startPos, const PatternPosition& endPos); void setSelectedRectangle(const PatternPosition& start, const PatternPosition& end); bool isSelectedCell(int trackNum, int colNum, int orderNum, int stepNum); void showPatternContextMenu(const PatternPosition& pos, const QPoint& point); // Layout decypherer JamKey getJamKeyFromLayoutMapping(Qt::Key key); }; #endif // PATTERN_EDITOR_PANEL_HPP BambooTracker-0.3.5/BambooTracker/gui/pattern_editor/pattern_position.hpp000066400000000000000000000050661362177441300267050ustar00rootroot00000000000000#ifndef PATTERN_POSITION_HPP #define PATTERN_POSITION_HPP struct PatternPosition { int track, colInTrack, order, step; friend bool operator==(const PatternPosition& a, const PatternPosition& b); friend bool operator!=(const PatternPosition& a, const PatternPosition& b); void setCols(int track, int colInTrack); void setRows(int order, int step); int compareCols(const PatternPosition& b) const; int compareRows(const PatternPosition& b) const; static bool inRowRange(const PatternPosition& pos, const PatternPosition& begin, const PatternPosition& last) { return (begin.compareRows(pos) <= 0 && pos.compareRows(last) <= 0); } static bool inRowRange(const PatternPosition& srcBegin, const PatternPosition& srcLast, const PatternPosition& tgtBegin, const PatternPosition& tgtLast) { if (PatternPosition::inRowRange(srcBegin, tgtBegin, tgtLast)) return true; else if (PatternPosition::inRowRange(srcLast, tgtBegin, tgtLast)) return true; else return (srcBegin.compareRows(tgtBegin) < 0 && tgtLast.compareRows(srcLast)); } }; inline bool operator==(const PatternPosition& a, const PatternPosition& b) { return ((a.track == b.track) && (a.colInTrack == b.colInTrack) && (a.order == b.order) && (a.step == b.step)); } inline bool operator!=(const PatternPosition& a, const PatternPosition& b) { return !(a == b); } inline void PatternPosition::setCols(int track, int colInTrack) { this->track = track; this->colInTrack = colInTrack; } inline void PatternPosition::setRows(int order, int step) { this->order = order; this->step = step; } /// Return: /// -2: this->track < b.track /// -1: this->track == b.track && this->colInTrack < b.colInTrack /// 0: this->track == b.track && this->colInTrack == b.colInTrack /// 1: this->track == b.track && this->colInTrack > b.colInTrack /// 2: this->track > b.track inline int PatternPosition::compareCols(const PatternPosition& b) const { if (track < b.track) return -2; else if (track > b.track) return 2; else { if (colInTrack < b.colInTrack) return -1; else if (colInTrack > b.colInTrack) return 1; else return 0; } } /// Return: /// -2: this->order < b.order /// -1: this->order == b.order && this->step < b.step /// 0: *this == b /// 1: this->order == b.order && this->step > b.step /// 2: this->order > b.order inline int PatternPosition::compareRows(const PatternPosition& b) const { if (order < b.order) return -2; else if (order > b.order) return 2; else { if (step < b.step) return -1; else if (step > b.step) return 1; else return 0; } } #endif // PATTERN_POSITION_HPP BambooTracker-0.3.5/BambooTracker/gui/q_application_wrapper.cpp000066400000000000000000000007411362177441300246320ustar00rootroot00000000000000#include "q_application_wrapper.hpp" #include #include QApplicationWrapper::QApplicationWrapper(int& argc, char** argv) : QApplication (argc, argv) {} bool QApplicationWrapper::notify(QObject* receiver, QEvent* event) { try { return QApplication::notify(receiver, event); } catch (std::exception& e) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occured.\n%1").arg(e.what())); return false; } } BambooTracker-0.3.5/BambooTracker/gui/q_application_wrapper.hpp000066400000000000000000000004471362177441300246420ustar00rootroot00000000000000#ifndef Q_APPLICATION_WRAPPER_HPP #define Q_APPLICATION_WRAPPER_HPP #include class QApplicationWrapper : public QApplication { public: QApplicationWrapper(int& argc, char** argv); bool notify(QObject* receiver, QEvent* event) override; }; #endif // Q_APPLICATION_WRAPPER_HPP BambooTracker-0.3.5/BambooTracker/gui/s98_export_settings_dialog.cpp000066400000000000000000000060361362177441300255350ustar00rootroot00000000000000#include "s98_export_settings_dialog.hpp" #include "ui_s98_export_settings_dialog.h" #include "export_handler.hpp" S98ExportSettingsDialog::S98ExportSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::S98ExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); for (QRadioButton *button : { ui->ym2608RadioButton, ui->ym2612RadioButton, ui->ym2203RadioButton, ui->internalSsgRadioButton, ui->ay8910PsgRadioButton }) connect(button, &QAbstractButton::toggled, this, &S98ExportSettingsDialog::updateSupportInformation); updateSupportInformation(); } S98ExportSettingsDialog::~S98ExportSettingsDialog() { delete ui; } int S98ExportSettingsDialog::getResolution() const { return ui->resSpinBox->value(); } bool S98ExportSettingsDialog::enabledTag() const { return ui->tagGroupBox->isChecked(); } S98Tag S98ExportSettingsDialog::getS98Tag() const { S98Tag tag; tag.title = ui->titleLineEdit->text().toUtf8().toStdString(); tag.artist = ui->artistLineEdit->text().toUtf8().toStdString(); tag.game = ui->gameLineEdit->text().toUtf8().toStdString(); tag.year = ui->yearLineEdit->text().toUtf8().toStdString(); tag.genre = ui->genreLineEdit->text().toUtf8().toStdString(); tag.comment = ui->commentLineEdit->text().toUtf8().toStdString(); tag.copyright = ui->copyrightLineEdit->text().toUtf8().toStdString(); tag.s98by = ui->s98byLineEdit->text().toUtf8().toStdString(); tag.system = ui->systemLineEdit->text().toUtf8().toStdString(); return tag; } int S98ExportSettingsDialog::getExportTarget() const { int target = 0; if (ui->ym2612RadioButton->isChecked()) target |= Export_YM2612; else if (ui->ym2203RadioButton->isChecked()) target |= Export_YM2203; if (ui->ay8910PsgRadioButton->isChecked()) target |= Export_AY8910Psg; else if (ui->ym2149PsgRadioButton->isChecked()) target |= Export_YM2149Psg; return target; } void S98ExportSettingsDialog::updateSupportInformation() { int target = getExportTarget(); int channels; int fm = target & Export_FmMask; int ssg = target & Export_SsgMask; switch (fm) { default: channels = 6; break; case Export_YM2203: channels = 3; break; } bool haveSsg = fm == Export_YM2608 || fm == Export_YM2203 || ssg != Export_InternalSsg; bool haveRhythm = fm == Export_YM2608; bool haveAdpcm = fm == Export_YM2608; ui->supportFmChannelsLabel->setText(QString::number(channels)); ui->supportSsgLabel->setText(haveSsg ? tr("Yes") : tr("No")); ui->supportRhythmLabel->setText(haveRhythm ? tr("Yes") : tr("No")); ui->supportAdpcmLabel->setText(haveAdpcm ? tr("Yes") : tr("No")); QPalette normalPalette = palette(); QPalette warnPalette = normalPalette; warnPalette.setColor(QPalette::WindowText, QColor(0xef2929)); ui->supportFmChannelsLabel->setPalette((channels == 6) ? normalPalette : warnPalette); ui->supportSsgLabel->setPalette(haveSsg ? normalPalette : warnPalette); ui->supportRhythmLabel->setPalette(haveRhythm ? normalPalette : warnPalette); ui->supportAdpcmLabel->setPalette(haveAdpcm ? normalPalette : warnPalette); } BambooTracker-0.3.5/BambooTracker/gui/s98_export_settings_dialog.hpp000066400000000000000000000011061362177441300255330ustar00rootroot00000000000000#ifndef S98_EXPORT_SETTINGS_DIALOG_HPP #define S98_EXPORT_SETTINGS_DIALOG_HPP #include #include "s98_tag.hpp" namespace Ui { class S98ExportSettingsDialog; } class S98ExportSettingsDialog : public QDialog { Q_OBJECT public: explicit S98ExportSettingsDialog(QWidget *parent = nullptr); ~S98ExportSettingsDialog(); int getResolution() const; bool enabledTag() const; S98Tag getS98Tag() const; int getExportTarget() const; private slots: void updateSupportInformation(); private: Ui::S98ExportSettingsDialog *ui; }; #endif // S98_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/s98_export_settings_dialog.ui000066400000000000000000000304021362177441300253620ustar00rootroot00000000000000 S98ExportSettingsDialog 0 0 491 521 S98 export settings Resolution Hz 1 100000 1000 Tag true Title Artist Game Year Genre Comment Copyright S98by System NEC PC-9801 Target FM YM2608 OPNA true YM2612 OPN2 YM2203 OPN Qt::Vertical 20 40 SSG OPN internal true AY-3-8910 PSG YM2149 PSG Qt::Vertical 20 40 Support FM Channels QFrame::WinPanel QFrame::Sunken 6 Qt::PlainText Qt::AlignCenter SSG QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Drums QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter ADPCM QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok resSpinBox tagGroupBox titleLineEdit artistLineEdit gameLineEdit yearLineEdit genreLineEdit commentLineEdit copyrightLineEdit s98byLineEdit systemLineEdit ym2608RadioButton ym2612RadioButton ym2203RadioButton internalSsgRadioButton ay8910PsgRadioButton ym2149PsgRadioButton buttonBox accepted() S98ExportSettingsDialog accept() 248 254 157 274 buttonBox rejected() S98ExportSettingsDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/slider_style.cpp000066400000000000000000000005121362177441300227450ustar00rootroot00000000000000#include "slider_style.hpp" int SliderStyle::styleHint (StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const { if (hint == QStyle::SH_Slider_AbsoluteSetButtons) { return Qt::LeftButton; } else { return QProxyStyle::styleHint(hint, option, widget, returnData); } } BambooTracker-0.3.5/BambooTracker/gui/slider_style.hpp000066400000000000000000000005021362177441300227510ustar00rootroot00000000000000#ifndef SLIDER_STYLE_HPP #define SLIDER_STYLE_HPP #include class SliderStyle : public QProxyStyle { public: virtual int styleHint (StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const; }; #endif // SLIDER_STYLE_HPP BambooTracker-0.3.5/BambooTracker/gui/vgm_export_settings_dialog.cpp000066400000000000000000000123011362177441300256730ustar00rootroot00000000000000#include "vgm_export_settings_dialog.hpp" #include "ui_vgm_export_settings_dialog.h" #include "export_handler.hpp" #include VgmExportSettingsDialog::VgmExportSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::VgmExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); for (QRadioButton *button : { ui->ym2608RadioButton, ui->ym2612RadioButton, ui->ym2203RadioButton, ui->internalSsgRadioButton, ui->ay8910PsgRadioButton }) connect(button, &QAbstractButton::toggled, this, &VgmExportSettingsDialog::updateSupportInformation); updateSupportInformation(); } VgmExportSettingsDialog::~VgmExportSettingsDialog() { delete ui; } bool VgmExportSettingsDialog::enabledGD3() const { return ui->gd3GroupBox->isChecked(); } QString VgmExportSettingsDialog::getTrackNameEnglish() const { return ui->titleEnLineEdit->text(); } QString VgmExportSettingsDialog::getTrackNameJapanese() const { return ui->titleJpLineEdit->text(); } QString VgmExportSettingsDialog::getGameNameEnglish() const { return ui->nameEnLineEdit->text(); } QString VgmExportSettingsDialog::getGameNameJapanese() const { return ui->nameJpLineEdit->text(); } QString VgmExportSettingsDialog::getSystemNameEnglish() const { return ui->systemEnLineEdit->text(); } QString VgmExportSettingsDialog::getSystemNameJapanese() const { return ui->systemJpLineEdit->text(); } QString VgmExportSettingsDialog::getTrackAuthorEnglish() const { return ui->authorEnLineEdit->text(); } QString VgmExportSettingsDialog::getTrackAuthorJapanese() const { return ui->authorJpLineEdit->text(); } QString VgmExportSettingsDialog::getReleaseDate() const { return ui->releaseDateLineEdit->text(); } QString VgmExportSettingsDialog::getVgmCreator() const { return ui->creatorLineEdit->text(); } QString VgmExportSettingsDialog::getNotes() const { return ui->notesPlainTextEdit->toPlainText(); } GD3Tag VgmExportSettingsDialog::getGD3Tag() const { GD3Tag tag; QTextCodec* sjis = QTextCodec::codecForName("Shift-JIS"); std::string endNull = ""; endNull += '\0'; endNull += '\0'; tag.trackNameEn = ""; for (auto c : getTrackNameEnglish().toLatin1()) { tag.trackNameEn += c; tag.trackNameEn += '\0'; } tag.trackNameEn += endNull; tag.trackNameJp = ""; for (auto c : sjis->fromUnicode(getTrackNameJapanese())) { tag.trackNameJp += c; } tag.trackNameJp += endNull; tag.gameNameEn = ""; for (auto c : getGameNameEnglish().toLatin1()) { tag.gameNameEn += c; tag.gameNameEn += '\0'; } tag.gameNameEn += endNull; tag.gameNameJp = ""; for (auto c : sjis->fromUnicode(getGameNameJapanese())) { tag.gameNameJp += c; } tag.gameNameJp += endNull; tag.systemNameEn = ""; for (auto c : getSystemNameEnglish().toLatin1()) { tag.systemNameEn += c; tag.systemNameEn += '\0'; } tag.systemNameEn += endNull; tag.systemNameJp = ""; for (auto c : sjis->fromUnicode(getSystemNameJapanese())) { tag.systemNameJp += c; } tag.systemNameJp += endNull; tag.authorEn = ""; for (auto c : getTrackAuthorEnglish().toLatin1()) { tag.authorEn += c; tag.authorEn += '\0'; } tag.authorEn += endNull; tag.authorJp = ""; for (auto c : sjis->fromUnicode(getTrackAuthorJapanese())) { tag.authorJp += c; } tag.authorJp += endNull; tag.releaseDate = ""; for (auto c : getReleaseDate().toLatin1()) { tag.releaseDate += c; tag.releaseDate += '\0'; } tag.releaseDate += endNull; tag.vgmCreator = ""; for (auto c : getVgmCreator().toLatin1()) { tag.vgmCreator += c; tag.vgmCreator += '\0'; } tag.vgmCreator += endNull; tag.notes = ""; for (auto c : getNotes().toLatin1()) { tag.notes += c; tag.notes += '\0'; } tag.notes += endNull; return tag; } int VgmExportSettingsDialog::getExportTarget() const { int target = 0; if (ui->ym2612RadioButton->isChecked()) target |= Export_YM2612; else if (ui->ym2203RadioButton->isChecked()) target |= Export_YM2203; if (ui->ay8910PsgRadioButton->isChecked()) target |= Export_AY8910Psg; else if (ui->ym2149PsgRadioButton->isChecked()) target |= Export_YM2149Psg; return target; } void VgmExportSettingsDialog::updateSupportInformation() { int target = getExportTarget(); int channels; int fm = target & Export_FmMask; int ssg = target & Export_SsgMask; switch (fm) { default: channels = 6; break; case Export_YM2203: channels = 3; break; } bool haveSsg = fm == Export_YM2608 || fm == Export_YM2203 || ssg != Export_InternalSsg; bool haveRhythm = fm == Export_YM2608; bool haveAdpcm = fm == Export_YM2608; ui->supportFmChannelsLabel->setText(QString::number(channels)); ui->supportSsgLabel->setText(haveSsg ? tr("Yes") : tr("No")); ui->supportRhythmLabel->setText(haveRhythm ? tr("Yes") : tr("No")); ui->supportAdpcmLabel->setText(haveAdpcm ? tr("Yes") : tr("No")); QPalette normalPalette = palette(); QPalette warnPalette = normalPalette; warnPalette.setColor(QPalette::WindowText, QColor(0xef2929)); ui->supportFmChannelsLabel->setPalette((channels == 6) ? normalPalette : warnPalette); ui->supportSsgLabel->setPalette(haveSsg ? normalPalette : warnPalette); ui->supportRhythmLabel->setPalette(haveRhythm ? normalPalette : warnPalette); ui->supportAdpcmLabel->setPalette(haveAdpcm ? normalPalette : warnPalette); } BambooTracker-0.3.5/BambooTracker/gui/vgm_export_settings_dialog.hpp000066400000000000000000000017211362177441300257040ustar00rootroot00000000000000#ifndef VGM_EXPORT_SETTINGS_DIALOG_HPP #define VGM_EXPORT_SETTINGS_DIALOG_HPP #include #include #include "gd3_tag.hpp" namespace Ui { class VgmExportSettingsDialog; } class VgmExportSettingsDialog : public QDialog { Q_OBJECT public: explicit VgmExportSettingsDialog(QWidget *parent = nullptr); ~VgmExportSettingsDialog(); bool enabledGD3() const; QString getTrackNameEnglish() const; QString getTrackNameJapanese() const; QString getGameNameEnglish() const; QString getGameNameJapanese() const; QString getSystemNameEnglish() const; QString getSystemNameJapanese() const; QString getTrackAuthorEnglish() const; QString getTrackAuthorJapanese() const; QString getReleaseDate() const; QString getVgmCreator() const; QString getNotes() const; GD3Tag getGD3Tag() const; int getExportTarget() const; private slots: void updateSupportInformation(); private: Ui::VgmExportSettingsDialog *ui; }; #endif // VGM_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/vgm_export_settings_dialog.ui000066400000000000000000000447171362177441300255460ustar00rootroot00000000000000 VgmExportSettingsDialog 0 0 491 622 VGM export settings GD3 tag true 6 6 6 6 3 Game 0 0 70 0 70 16777215 Name English 0 0 70 0 70 16777215 Release date Qt::Horizontal 152 20 Japanese 0 0 70 0 70 16777215 System Japanese NEC PC-9801 English Track 0 0 70 0 70 16777215 Title English Japanese 0 0 70 0 70 16777215 Author English Japanese VGM file 0 0 70 0 70 16777215 Creator Qt::Horizontal 149 20 0 0 70 0 70 16777215 Notes Target FM YM2608 OPNA true YM2612 OPN2 YM2203 OPN Qt::Vertical 20 40 SSG OPN internal true AY-3-8910 PSG YM2149 PSG Qt::Vertical 20 40 Support FM Channels QFrame::WinPanel QFrame::Sunken 6 Qt::PlainText Qt::AlignCenter SSG QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Drums QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter ADPCM QFrame::WinPanel QFrame::Sunken Yes Qt::PlainText Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok gd3GroupBox titleEnLineEdit titleJpLineEdit authorEnLineEdit authorJpLineEdit nameEnLineEdit nameJpLineEdit systemEnLineEdit systemJpLineEdit releaseDateLineEdit creatorLineEdit notesPlainTextEdit buttonBox accepted() VgmExportSettingsDialog accept() 248 254 157 274 buttonBox rejected() VgmExportSettingsDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/wave_export_settings_dialog.cpp000066400000000000000000000013371362177441300260530ustar00rootroot00000000000000#include "wave_export_settings_dialog.hpp" #include "ui_wave_export_settings_dialog.h" WaveExportSettingsDialog::WaveExportSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::WaveExportSettingsDialog) { ui->setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); ui->sampleRateComboBox->addItem("44100Hz", 44100); ui->sampleRateComboBox->addItem("48000Hz", 48000); ui->sampleRateComboBox->addItem("55466Hz", 55466); } WaveExportSettingsDialog::~WaveExportSettingsDialog() { delete ui; } int WaveExportSettingsDialog::getSampleRate() const { return ui->sampleRateComboBox->currentData().toInt(); } int WaveExportSettingsDialog::getLoopCount() const { return ui->loopSpinBox->value(); } BambooTracker-0.3.5/BambooTracker/gui/wave_export_settings_dialog.hpp000066400000000000000000000007151362177441300260570ustar00rootroot00000000000000#ifndef WAVE_EXPORT_SETTINGS_DIALOG_HPP #define WAVE_EXPORT_SETTINGS_DIALOG_HPP #include namespace Ui { class WaveExportSettingsDialog; } class WaveExportSettingsDialog : public QDialog { Q_OBJECT public: explicit WaveExportSettingsDialog(QWidget *parent = nullptr); ~WaveExportSettingsDialog(); int getSampleRate() const; int getLoopCount() const; private: Ui::WaveExportSettingsDialog *ui; }; #endif // WAVE_EXPORT_SETTINGS_DIALOG_HPP BambooTracker-0.3.5/BambooTracker/gui/wave_export_settings_dialog.ui000066400000000000000000000043031362177441300257020ustar00rootroot00000000000000 WaveExportSettingsDialog 0 0 180 100 WAV export settings Sample rate Loop 1 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok sampleRateComboBox loopSpinBox buttonBox accepted() WaveExportSettingsDialog accept() 248 254 157 274 buttonBox rejected() WaveExportSettingsDialog reject() 316 260 286 274 BambooTracker-0.3.5/BambooTracker/gui/wave_visual.cpp000066400000000000000000000033111362177441300225700ustar00rootroot00000000000000#include "gui/wave_visual.hpp" #include "gui/color_palette.hpp" #include #include //Xcode 8.3: "no member names 'abs' in namespace 'std' #include WaveVisual::WaveVisual(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); } void WaveVisual::setColorPalette(std::shared_ptr palette) { palette_ = palette; } void WaveVisual::setStereoSamples(const int16_t *buffer, size_t frames) { // identify the highest of the 2 input signals int32_t sum = 0; for (size_t i = 0; i < frames; ++i) { size_t p = i << 1; sum += std::abs(buffer[p]); sum -= std::abs(buffer[p + 1]); } // use this signal as display data samples_.resize(frames); int16_t *samples = samples_.data(); for (size_t i = 0; i < frames; ++i) samples[i] = buffer[(i << 1) + (sum < 0)]; repaint(); } void WaveVisual::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter painter(this); if (!palette_) return; int w = width(); int h = height(); int centerh = h >> 1; painter.fillRect(0, 0, w, h, palette_->wavBackColor); const int16_t *samples = samples_.data(); size_t frames = samples_.size(); if (frames <= 0) return; painter.setPen(palette_->wavDrawColor); int lastY = centerh; const int16_t range = std::numeric_limits::max() >> 1; for (int x = 0; x < w; ++x) { size_t index = (size_t)(x * ((double)frames / w)); int16_t sample = samples[index]; int y = centerh - (centerh * sample / range); painter.drawPoint(x, y); // draw intermediate points int y1 = lastY, y2 = y; int yM = (y1 + y2) >> 1; while (y1 != y2) { bool b = (y1 > yM) ^ (y1 > y2); painter.drawPoint(x - !b, y1); y1 += (y1 < y2) ? 1 : -1; } lastY = y; } } BambooTracker-0.3.5/BambooTracker/gui/wave_visual.hpp000066400000000000000000000010411362177441300225730ustar00rootroot00000000000000#ifndef WAVE_VISUAL_HPP #define WAVE_VISUAL_HPP #include #include #include #include class ColorPalette; class WaveVisual : public QWidget { Q_OBJECT public: explicit WaveVisual(QWidget *parent = nullptr); void setColorPalette(std::shared_ptr palette); void setStereoSamples(const int16_t *buffer, size_t frames); protected: void paintEvent(QPaintEvent *event) override; private: std::shared_ptr palette_; std::vector samples_; }; #endif // WAVE_VISUAL_HPP BambooTracker-0.3.5/BambooTracker/instrument/000077500000000000000000000000001362177441300211655ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/instrument/abstract_instrument_property.cpp000066400000000000000000000017171362177441300277360ustar00rootroot00000000000000#include "abstract_instrument_property.hpp" #include AbstractInstrumentProperty::AbstractInstrumentProperty(int num) : num_(num) { } AbstractInstrumentProperty::AbstractInstrumentProperty(const AbstractInstrumentProperty& other) { num_ = other.num_; users_ = other.users_; } void AbstractInstrumentProperty::setNumber(int num) { num_ = num; } int AbstractInstrumentProperty::getNumber() const { return num_; } void AbstractInstrumentProperty::registerUserInstrument(int instNum) { users_.push_back(instNum); std::sort(users_.begin(), users_.end()); } void AbstractInstrumentProperty::deregisterUserInstrument(int instNum) { users_.erase(std::find(users_.begin(), users_.end(), instNum)); } bool AbstractInstrumentProperty::isUserInstrument() const { return !users_.empty(); } std::vector AbstractInstrumentProperty::getUserInstruments() const { return users_; } void AbstractInstrumentProperty::clearUserInstruments() { users_.clear(); } BambooTracker-0.3.5/BambooTracker/instrument/abstract_instrument_property.hpp000066400000000000000000000010731362177441300277360ustar00rootroot00000000000000#pragma once #include #include class AbstractInstrumentProperty { public: virtual ~AbstractInstrumentProperty() = default; void setNumber(int num); int getNumber() const; void registerUserInstrument(int instNum); void deregisterUserInstrument(int instNum); bool isUserInstrument() const; std::vector getUserInstruments() const; void clearUserInstruments(); protected: explicit AbstractInstrumentProperty(int num); AbstractInstrumentProperty(const AbstractInstrumentProperty& other); private: int num_; std::vector users_; }; BambooTracker-0.3.5/BambooTracker/instrument/bank.cpp000066400000000000000000000052421362177441300226070ustar00rootroot00000000000000#include "bank.hpp" #include #include #include "instrument_io.hpp" #include "format/wopn_file.h" BtBank::BtBank(std::vector ids, std::vector names) : ids_(std::move(ids)), names_(std::move(names)) { } BtBank::BtBank(std::vector ids, std::vector names, std::vector instSecs, BinaryContainer propSec, uint32_t version) : instCtrs_(std::move(instSecs)), propCtr_(std::move(propSec)), ids_(std::move(ids)), names_(std::move(names)), version_(version) { } BtBank::~BtBank() { } size_t BtBank::getNumInstruments() const { return ids_.size(); } std::string BtBank::getInstrumentIdentifier(size_t index) const { return std::to_string(ids_.at(index)); } std::string BtBank::getInstrumentName(size_t index) const { return names_.at(index); } AbstractInstrument* BtBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { return InstrumentIO::loadBTBInstrument(instCtrs_.at(static_cast(index)), propCtr_, instMan, instNum, version_); } /******************************/ void WopnBank::WOPNDeleter::operator()(WOPNFile *x) { WOPN_Free(x); } struct WopnBank::InstEntry { WOPNInstrument *inst; struct { bool percussive : 1; unsigned msb : 7; unsigned lsb : 7; unsigned nth : 7; }; }; WopnBank::WopnBank(WOPNFile *wopn) : wopn_(wopn) { unsigned numM = wopn->banks_count_melodic; unsigned numP = wopn->banks_count_percussion; size_t instMax = 128 * (numP + numM); entries_.reserve(instMax); for (size_t i = 0; i < instMax; ++i) { InstEntry ent; ent.percussive = (i / 128) >= numM; WOPNBank &bank = ent.percussive ? wopn->banks_percussive[(i / 128) - numM] : wopn->banks_melodic[i / 128]; ent.msb = bank.bank_midi_msb; ent.lsb = bank.bank_midi_lsb; ent.nth = i % 128; ent.inst = &bank.ins[ent.nth]; if ((ent.inst->inst_flags & WOPN_Ins_IsBlank) == 0) entries_.push_back(ent); } entries_.shrink_to_fit(); } WopnBank::~WopnBank() { } size_t WopnBank::getNumInstruments() const { return entries_.size(); } std::string WopnBank::getInstrumentIdentifier(size_t index) const { const InstEntry &ent = entries_.at(index); char identifier[64]; sprintf(identifier, "%c%03d:%03d:%03d", "MP"[ent.percussive], ent.msb, ent.lsb, ent.nth); return identifier; } std::string WopnBank::getInstrumentName(size_t index) const { const InstEntry &ent = entries_.at(index); return ent.inst->inst_name; } AbstractInstrument* WopnBank::loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const { const InstEntry &ent = entries_.at(index); return InstrumentIO::loadWOPNInstrument(*ent.inst, instMan, instNum); } BambooTracker-0.3.5/BambooTracker/instrument/bank.hpp000066400000000000000000000034571362177441300226220ustar00rootroot00000000000000#pragma once #include #include #include "io/binary_container.hpp" class AbstractInstrument; class InstrumentsManager; struct WOPNFile; class AbstractBank { public: virtual ~AbstractBank() = default; virtual size_t getNumInstruments() const = 0; virtual std::string getInstrumentIdentifier(size_t index) const = 0; virtual std::string getInstrumentName(size_t index) const = 0; virtual AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const = 0; }; class BtBank : public AbstractBank { public: BtBank(std::vector ids, std::vector names); BtBank(std::vector ids, std::vector names, std::vector instSecs, BinaryContainer propSec, uint32_t version); ~BtBank() override; size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: std::vector instCtrs_; BinaryContainer propCtr_; std::vector ids_; std::vector names_; uint32_t version_; }; class WopnBank : public AbstractBank { public: explicit WopnBank(WOPNFile *wopn); ~WopnBank() override; size_t getNumInstruments() const override; std::string getInstrumentIdentifier(size_t index) const override; std::string getInstrumentName(size_t index) const override; AbstractInstrument* loadInstrument(size_t index, std::weak_ptr instMan, int instNum) const override; private: struct WOPNDeleter { void operator()(WOPNFile *x); }; std::unique_ptr wopn_; struct InstEntry; std::vector entries_; }; BambooTracker-0.3.5/BambooTracker/instrument/command_sequence.cpp000066400000000000000000000155671362177441300252150ustar00rootroot00000000000000#include "command_sequence.hpp" CommandSequence::CommandSequence(int num, SequenceType seqType, int comType, int comData) : AbstractInstrumentProperty(num), DEF_COM_TYPE(comType), DEF_COM_DATA(comData), type_(seqType), release_{ ReleaseType::NoRelease, -1 } { seq_.push_back({ comType, comData }); } CommandSequence::CommandSequence(const CommandSequence& other) : AbstractInstrumentProperty(other), DEF_COM_TYPE(other.DEF_COM_TYPE), DEF_COM_DATA(other.DEF_COM_DATA), type_(other.type_), seq_(other.seq_), loops_(other.loops_), release_(other.release_) { } bool operator==(const CommandSequence& a, const CommandSequence& b) { return (a.type_ == b.type_ && a.seq_ == b.seq_ && a.loops_ == b.loops_ && a.release_ == b.release_); } std::unique_ptr CommandSequence::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } void CommandSequence::setType(SequenceType type) { type_ = type; } SequenceType CommandSequence::getType() const { return type_; } size_t CommandSequence::getSequenceSize() const { return seq_.size(); } int CommandSequence::getSequenceTypeAt(int n) { return seq_.at(static_cast(n)).type; } int CommandSequence::getSequenceDataAt(int n) { return seq_.at(static_cast(n)).data; } std::vector CommandSequence::getSequence() const { return seq_; } void CommandSequence::addSequenceCommand(int type, int data) { seq_.push_back({ type, data }); } void CommandSequence::removeSequenceCommand() { seq_.pop_back(); // Modify loop if (!loops_.empty()) { if (static_cast(seq_.size()) == loops_.back().begin) { loops_.pop_back(); } else if (static_cast(seq_.size()) == loops_.back().end) { --loops_.back().end; } } // Modify release if (release_.begin == static_cast(seq_.size())) release_.begin = -1; } void CommandSequence::setSequenceCommand(int n, int type, int data) { seq_.at(static_cast(n)) = { type, data }; } size_t CommandSequence::getNumberOfLoops() const { return loops_.size(); } int CommandSequence::getBeginningCountOfLoop(int n) { return loops_.at(static_cast(n)).begin; } int CommandSequence::getEndCountOfLoop(int n) { return loops_.at(static_cast(n)).end; } int CommandSequence::getTimesOfLoop(int n) { return loops_.at(static_cast(n)).times; } std::vector CommandSequence::getLoops() const { return loops_; } void CommandSequence::setLoops(std::vector begins, std::vector ends, std::vector times) { loops_.clear(); for (size_t i = 0; i < begins.size(); ++i) { loops_.push_back({ begins.at(i), ends.at(i), times.at(i) }); } } int CommandSequence::getReleaseBeginningCount() const { return release_.begin; } ReleaseType CommandSequence::getReleaseType() const { return release_.type; } Release CommandSequence::getRelease() const { return release_; } void CommandSequence::setRelease(ReleaseType type, int begin) { release_ = { type, begin }; } std::unique_ptr CommandSequence::getIterator() { return std::unique_ptr(std::make_unique(this)); } bool CommandSequence::isEdited() const { return (seq_.size() != 1 || seq_.front().type != DEF_COM_TYPE || seq_.front().data != DEF_COM_DATA || loops_.size() || release_.begin > -1); } /****************************************/ CommandSequence::Iterator::Iterator(CommandSequence* seq) : seq_(seq), pos_(0), started_(false), isRelease_(false), relReleaseRatio_(1) { } int CommandSequence::Iterator::getPosition() const { return pos_; } int CommandSequence::Iterator::getSequenceType() const { return seq_->type_; } int CommandSequence::Iterator::getCommandType() const { return (pos_ == -1 || pos_ >= seq_->getSequenceSize()) ? -1 : isRelease_ ? static_cast(seq_->getSequenceTypeAt(pos_) * relReleaseRatio_) : seq_->getSequenceTypeAt(pos_); } int CommandSequence::Iterator::getCommandData() const { return (pos_ == -1 || pos_ >= seq_->getSequenceSize()) ? -1 : seq_->getSequenceDataAt(pos_); } int CommandSequence::Iterator::next(bool isReleaseBegin) { if (!isReleaseBegin && pos_ == -1) return -1; if (!started_) { started_ = true; return pos_; } int next = -1; if (isReleaseBegin) { loopStack_.clear(); isRelease_ = true; switch (seq_->release_.type) { case ReleaseType::NoRelease: break; case ReleaseType::FixedRelease: next = seq_->release_.begin; break; case ReleaseType::AbsoluteRelease: { int crtr; if (pos_ == -1) { int prevIdx = seq_->release_.begin - 1; if (prevIdx < 0) { next = seq_->release_.begin; break; } else { crtr = seq_->seq_[static_cast(prevIdx)].type; } } else { crtr = seq_->seq_[static_cast(pos_)].type; } for (size_t i = static_cast(seq_->release_.begin); i < seq_->seq_.size(); ++i) { if (seq_->seq_[i].type <= crtr) { next = static_cast(i); break; } } break; } case ReleaseType::RelativeRelease: { if (pos_ == -1) { int prevIdx = seq_->release_.begin - 1; if (prevIdx >= 0) relReleaseRatio_ = seq_->seq_[static_cast(prevIdx)].type / 15.0f; } else { relReleaseRatio_ = seq_->seq_[static_cast(pos_)].type / 15.0f; } next = seq_->release_.begin; break; } } } else { next = pos_ + 1; } while (!loopStack_.empty()) { if (pos_ == loopStack_.back().end) { if (loopStack_.back().times < 0) { // Infinity loop next = loopStack_.back().begin; break; } else { if (loopStack_.back().times) { next = loopStack_.back().begin; --loopStack_.back().times; break; } else { loopStack_.pop_back(); } } } else { break; } } for (auto& l : seq_->loops_) { if (next < l.begin) break; else if (next == l.begin) { if (loopStack_.empty()) { loopStack_.push_back({ l.begin, l.end, (l.times == 1) ? -1 : (l.times - 1)}); } else { bool flag = true; for (auto& lp : loopStack_) { if (lp.begin == l.begin && lp.end == l.end) { flag = false; break; } } if (flag) { loopStack_.push_back({ l.begin, l.end, (l.times == 1) ? -1 : (l.times - 1)}); } } } } pos_ = next; if (!isRelease_ && pos_ == seq_->release_.begin) { pos_ = -1; } else if (pos_ == static_cast(seq_->seq_.size())) { pos_ = -1; } return pos_; } int CommandSequence::Iterator::front() { started_ = true; loopStack_.clear(); isRelease_ = false; relReleaseRatio_ = 1; if (seq_->release_.begin == 0) { pos_ = -1; } else { pos_ = 0; for (auto& l : seq_->loops_) { if (pos_ < l.begin) break; else if (pos_ == l.begin) { loopStack_.push_back({ l.begin, l.end, (l.times == 1) ? -1 : (l.times - 1)}); } } } return pos_; } int CommandSequence::Iterator::end() { pos_ = -1; started_ = false; return -1; } BambooTracker-0.3.5/BambooTracker/instrument/command_sequence.hpp000066400000000000000000000104531362177441300252070ustar00rootroot00000000000000#pragma once #include #include #include #include "abstract_instrument_property.hpp" #include "sequence_iterator_interface.hpp" struct CommandSequenceUnit { int type; /// In SSG waveform and envelope, /// - If bit 17 is 0, /// * If bit 16 is 0, bit 0-15 is raw data /// * If bit 16 is 1, bit 0-7 is 2nd and bit 8-15 is 1st ratio value /// - If bit 17 is 1, /// * If bit 16 is 0, bit 0-15 is right shift value /// * If bit 16 is 1, bit 0-15 is left shift value int data; friend bool operator==(const CommandSequenceUnit& a, const CommandSequenceUnit& b) { return (a.type == b.type && a.data == b.data); } friend bool operator!=(const CommandSequenceUnit& a, const CommandSequenceUnit& b) { return !(a == b); } enum DataType : int { NODATA = -1, RAW, RATIO, LSHIFT, RSHIFT }; inline static DataType checkDataType(int data) { if (data < 0) return DataType::NODATA; else if (0x20000 & data) return (0x10000 & data ? DataType::LSHIFT : DataType::RSHIFT); else if (0x10000 & data) return DataType::RATIO; else return DataType::RAW; } inline static int ratio2data(int first, int second) { return ((1 << 16) | (first << 8) | second); } inline static std::pair data2ratio(int data) { return std::make_pair((data & 0x0000ff00) >> 8, data & 0x000000ff); } inline static int shift2data(int rshift) { if (rshift > 0) return ((2 << 16) | rshift); else return ((3 << 16) | -rshift); } /// Check whether data is left shift or right shift before call this method inline static int data2shift(int data) { return 0xffff & data; } }; enum SequenceType : int { NO_SEQUENCE_TYPE = -1, ABSOLUTE_SEQUENCE = 0, FIXED_SEQUENCE = 1, RELATIVE_SEQUENCE = 2 }; struct Loop { int begin, end, times; friend bool operator==(const Loop& a, const Loop& b) { return (a.begin == b.begin && a.end == b.end && a.times == b.times); } friend bool operator!=(const Loop& a, const Loop& b) { return !(a == b); } }; enum ReleaseType { NoRelease, FixedRelease, AbsoluteRelease, RelativeRelease }; struct Release { ReleaseType type; int begin; friend bool operator==(const Release& a, const Release& b) { return (a.type == b.type && a.begin == b.begin); } friend bool operator!=(const Release& a, const Release& b) { return !(a == b); } }; class CommandSequence : public AbstractInstrumentProperty { public: CommandSequence(int num, SequenceType seqType = SequenceType::NO_SEQUENCE_TYPE, int comType = 0, int comData = -1); CommandSequence(const CommandSequence& other); virtual ~CommandSequence() = default; friend bool operator==(const CommandSequence& a, const CommandSequence& b); friend bool operator!=(const CommandSequence& a, const CommandSequence& b) { return !(a == b); } std::unique_ptr clone(); /// 0: Absolute /// 1: Fix /// 2: Relative void setType(SequenceType type); SequenceType getType() const; size_t getSequenceSize() const; int getSequenceTypeAt(int n); int getSequenceDataAt(int n); std::vector getSequence() const; void addSequenceCommand(int type, int data); void removeSequenceCommand(); void setSequenceCommand(int n, int type, int data); size_t getNumberOfLoops() const; int getBeginningCountOfLoop(int n); int getEndCountOfLoop(int n); int getTimesOfLoop(int n); std::vector getLoops() const; void setLoops(std::vector begins, std::vector ends, std::vector times); int getReleaseBeginningCount() const; ReleaseType getReleaseType() const; Release getRelease() const; void setRelease(ReleaseType type, int begin); class Iterator : public SequenceIteratorInterface { public: explicit Iterator(CommandSequence* seq); int getPosition() const override; int getSequenceType() const override; int getCommandType() const override; int getCommandData() const override; int next(bool isReleaseBegin = false) override; int front() override; int end() override; private: CommandSequence* seq_; int pos_; bool started_; std::vector loopStack_; bool isRelease_; float relReleaseRatio_; }; std::unique_ptr getIterator(); bool isEdited() const; private: const int DEF_COM_TYPE; const int DEF_COM_DATA; SequenceType type_; std::vector seq_; std::vector loops_; Release release_; }; BambooTracker-0.3.5/BambooTracker/instrument/effect_iterator.cpp000066400000000000000000000057661362177441300250540ustar00rootroot00000000000000#include "effect_iterator.hpp" #include ArpeggioEffectIterator::ArpeggioEffectIterator(int second, int third) : pos_(2), started_(false), second_(second + 48), third_(third + 48) { } int ArpeggioEffectIterator::getPosition() const { return pos_; } int ArpeggioEffectIterator::getSequenceType() const { return 0; } int ArpeggioEffectIterator::getCommandType() const { switch (pos_) { case 0: return 48; case 1: return second_; case 2: return third_; default: return -1; } } int ArpeggioEffectIterator::getCommandData() const { return -1; } int ArpeggioEffectIterator::next(bool isReleaseBegin) { (void)isReleaseBegin; if (started_) { pos_ = (pos_ + 1) % 3; } else { started_ = true; } return pos_; } int ArpeggioEffectIterator::front() { pos_ = 0; started_ = true; return 0; } int ArpeggioEffectIterator::end() { pos_ = -1; started_ = false; return -1; } /****************************************/ WavingEffectIterator::WavingEffectIterator(int period, int depth) : started_(false) { for (int i = 0; i <= period; ++i) { seq_.push_back(i * depth); } for (size_t i = static_cast(period - 1); i > 0; --i) { seq_.push_back(seq_.at(i)); } size_t p2 = static_cast(period) << 1; for (size_t i = 0; i < p2; ++i) { seq_.push_back(-seq_.at(i)); } pos_ = static_cast(seq_.size()) - 1; } int WavingEffectIterator::getPosition() const { return pos_; } int WavingEffectIterator::getSequenceType() const { return 0; } int WavingEffectIterator::getCommandType() const { return seq_.at(static_cast(pos_)); } int WavingEffectIterator::getCommandData() const { return -1; } int WavingEffectIterator::next(bool isReleaseBegin) { (void)isReleaseBegin; if (started_) { pos_ = (pos_ + 1) % static_cast(seq_.size()); } else { started_ = true; } return pos_; } int WavingEffectIterator::front() { pos_ = 0; started_ = true; return 0; } int WavingEffectIterator::end() { pos_ = -1; started_ = false; return -1; } /****************************************/ NoteSlideEffectIterator::NoteSlideEffectIterator(int speed, int seminote) : started_(false) { int d = seminote * 32; if (speed) { int prev = 0; for (int i = 0; i <= speed; ++i) { int dif = d * i / speed - prev; seq_.push_back(dif); prev += dif; } } else { seq_.push_back(d); } pos_ = 0; } int NoteSlideEffectIterator::getPosition() const { return pos_; } int NoteSlideEffectIterator::getSequenceType() const { return 0; } int NoteSlideEffectIterator::getCommandType() const { return seq_.at(static_cast(pos_)); } int NoteSlideEffectIterator::getCommandData() const { return -1; } int NoteSlideEffectIterator::next(bool isReleaseBegin) { (void)isReleaseBegin; if (started_) { return (++pos_ < static_cast(seq_.size())) ? pos_ : -1; } else { started_ = true; return pos_; } } int NoteSlideEffectIterator::front() { pos_ = 0; started_ = true; return 0; } int NoteSlideEffectIterator::end() { pos_ = -1; started_ = false; return -1; } BambooTracker-0.3.5/BambooTracker/instrument/effect_iterator.hpp000066400000000000000000000025221362177441300250440ustar00rootroot00000000000000#pragma once #include #include "sequence_iterator_interface.hpp" class ArpeggioEffectIterator : public SequenceIteratorInterface { public: ArpeggioEffectIterator(int second, int third); int getPosition() const override; int getSequenceType() const override; int getCommandType() const override; int getCommandData() const override; int next(bool isReleaseBegin = false) override; int front() override; int end() override; private: int pos_; bool started_; int second_, third_; }; class WavingEffectIterator : public SequenceIteratorInterface { public: WavingEffectIterator(int period, int depth); int getPosition() const override; int getSequenceType() const override; int getCommandType() const override; int getCommandData() const override; int next(bool isReleaseBegin = false) override; int front() override; int end() override; private: int pos_; bool started_; std::vector seq_; }; class NoteSlideEffectIterator : public SequenceIteratorInterface { public: NoteSlideEffectIterator(int speed, int seminote); int getPosition() const override; int getSequenceType() const override; int getCommandType() const override; int getCommandData() const override; int next(bool isReleaseBegin = false) override; int front() override; int end() override; private: int pos_; bool started_; std::vector seq_; }; BambooTracker-0.3.5/BambooTracker/instrument/envelope_fm.cpp000066400000000000000000000071411362177441300241730ustar00rootroot00000000000000#include "envelope_fm.hpp" constexpr EnvelopeFM::FMOperator EnvelopeFM::DEF_OP[4]; EnvelopeFM::EnvelopeFM(int num) : AbstractInstrumentProperty (num), al_(DEF_AL), fb_(DEF_FB) { op_[0] = DEF_OP[0]; op_[1] = DEF_OP[1]; op_[2] = DEF_OP[2]; op_[3] = DEF_OP[3]; initParamMap(); } void EnvelopeFM::initParamMap() { paramMap_ = { { FMEnvelopeParameter::AL, al_ }, { FMEnvelopeParameter::FB, fb_ }, { FMEnvelopeParameter::AR1, op_[0].ar_ }, { FMEnvelopeParameter::DR1, op_[0].dr_ }, { FMEnvelopeParameter::SR1, op_[0].sr_ }, { FMEnvelopeParameter::RR1, op_[0].rr_ }, { FMEnvelopeParameter::SL1, op_[0].sl_ }, { FMEnvelopeParameter::TL1, op_[0].tl_ }, { FMEnvelopeParameter::KS1, op_[0].ks_ }, { FMEnvelopeParameter::ML1, op_[0].ml_ }, { FMEnvelopeParameter::DT1, op_[0].dt_ }, { FMEnvelopeParameter::SSGEG1, op_[0].ssgeg_ }, { FMEnvelopeParameter::AR2, op_[1].ar_ }, { FMEnvelopeParameter::DR2, op_[1].dr_ }, { FMEnvelopeParameter::SR2, op_[1].sr_ }, { FMEnvelopeParameter::RR2, op_[1].rr_ }, { FMEnvelopeParameter::SL2, op_[1].sl_ }, { FMEnvelopeParameter::TL2, op_[1].tl_ }, { FMEnvelopeParameter::KS2, op_[1].ks_ }, { FMEnvelopeParameter::ML2, op_[1].ml_ }, { FMEnvelopeParameter::DT2, op_[1].dt_ }, { FMEnvelopeParameter::SSGEG2, op_[1].ssgeg_ }, { FMEnvelopeParameter::AR3, op_[2].ar_ }, { FMEnvelopeParameter::DR3, op_[2].dr_ }, { FMEnvelopeParameter::SR3, op_[2].sr_ }, { FMEnvelopeParameter::RR3, op_[2].rr_ }, { FMEnvelopeParameter::SL3, op_[2].sl_ }, { FMEnvelopeParameter::TL3, op_[2].tl_ }, { FMEnvelopeParameter::KS3, op_[2].ks_ }, { FMEnvelopeParameter::ML3, op_[2].ml_ }, { FMEnvelopeParameter::DT3, op_[2].dt_ }, { FMEnvelopeParameter::SSGEG3, op_[2].ssgeg_ }, { FMEnvelopeParameter::AR4, op_[3].ar_ }, { FMEnvelopeParameter::DR4, op_[3].dr_ }, { FMEnvelopeParameter::SR4, op_[3].sr_ }, { FMEnvelopeParameter::RR4, op_[3].rr_ }, { FMEnvelopeParameter::SL4, op_[3].sl_ }, { FMEnvelopeParameter::TL4, op_[3].tl_ }, { FMEnvelopeParameter::KS4, op_[3].ks_ }, { FMEnvelopeParameter::ML4, op_[3].ml_ }, { FMEnvelopeParameter::DT4, op_[3].dt_ }, { FMEnvelopeParameter::SSGEG4, op_[3].ssgeg_ } }; } EnvelopeFM::EnvelopeFM(const EnvelopeFM &other) : AbstractInstrumentProperty (other) { al_ = other.al_; fb_ = other.fb_; for (int i = 0; i < 4; ++i) op_[i] = other.op_[i]; initParamMap(); } bool operator==(const EnvelopeFM& a, const EnvelopeFM& b) { if (a.al_ != b.al_ || a.fb_ != b.fb_) return false; for (int i = 0; i< 4; ++i) { if (a.op_[0] != b.op_[0]) return false; } return true; } std::unique_ptr EnvelopeFM::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } bool EnvelopeFM::getOperatorEnabled(int num) const { return op_[num].enabled_; } void EnvelopeFM::setOperatorEnabled(int num, bool enabled) { op_[num].enabled_ = enabled; } int EnvelopeFM::getParameterValue(FMEnvelopeParameter param) const { return paramMap_.at(param); } void EnvelopeFM::setParameterValue(FMEnvelopeParameter param, int value) { paramMap_.at(param) = value; } bool EnvelopeFM::isEdited() const { if (al_ != DEF_AL || fb_ != DEF_FB) return true; for (int i = 0; i < 4; ++i) { if (op_[i].enabled_ != DEF_OP[i].enabled_ || op_[i].ar_ != DEF_OP[i].ar_ || op_[i].dr_ != DEF_OP[i].dr_ || op_[i].sr_ != DEF_OP[i].sr_ || op_[i].rr_ != DEF_OP[i].rr_ || op_[i].sl_ != DEF_OP[i].sl_ || op_[i].tl_ != DEF_OP[i].tl_ || op_[i].ks_ != DEF_OP[i].ks_ || op_[i].ml_ != DEF_OP[i].ml_ || op_[i].dt_ != DEF_OP[i].dt_) return true; } return false; } BambooTracker-0.3.5/BambooTracker/instrument/envelope_fm.hpp000066400000000000000000000036771362177441300242120ustar00rootroot00000000000000#pragma once #include #include #include "abstract_instrument_property.hpp" #include "enum_hash.hpp" enum class FMEnvelopeParameter; class EnvelopeFM : public AbstractInstrumentProperty { public: explicit EnvelopeFM(int num); EnvelopeFM(const EnvelopeFM& other); friend bool operator==(const EnvelopeFM& a, const EnvelopeFM& b); friend bool operator!=(const EnvelopeFM& a, const EnvelopeFM& b) { return !(a == b); } std::unique_ptr clone(); bool getOperatorEnabled(int num) const; void setOperatorEnabled(int num, bool enabled); int getParameterValue(FMEnvelopeParameter param) const; void setParameterValue(FMEnvelopeParameter param, int value); bool isEdited() const; private: int al_; int fb_; struct FMOperator { bool enabled_; int ar_; int dr_; int sr_; int rr_; int sl_; int tl_; int ks_; int ml_; int dt_; int ssgeg_; // -1: No use friend bool operator==(const FMOperator& a, const FMOperator& b) { return (a.enabled_ == b.enabled_ && a.ar_ == b.ar_ && a.dr_ == b.dr_ && a.sr_ == b.sr_ && a.rr_ == b.rr_ && a.sl_ == b.sl_ && a.tl_ == b.tl_ && a.ks_ == b.ks_ && a.ml_ == b.ml_ && a.dt_ == b.dt_ && a.ssgeg_ == b.ssgeg_); } friend bool operator!=(const FMOperator& a, const FMOperator& b) { return !(a == b); } }; FMOperator op_[4]; static constexpr int DEF_AL = 4; static constexpr int DEF_FB = 0; static constexpr FMOperator DEF_OP[4] = { { true, 31, 0, 0, 7, 0, 32, 0, 0, 0, -1 }, { true, 31, 0, 0, 7, 0, 0, 0, 0, 0, -1 }, { true, 31, 0, 0, 7, 0, 32, 0, 0, 0, -1 }, { true, 31, 0, 0, 7, 0, 0, 0, 0, 0, -1 } }; std::unordered_map paramMap_; void initParamMap(); }; enum class FMEnvelopeParameter { AL, FB, AR1, DR1, SR1, RR1, SL1, TL1, KS1, ML1, DT1, AR2, DR2, SR2, RR2, SL2, TL2, KS2, ML2, DT2, AR3, DR3, SR3, RR3, SL3, TL3, KS3, ML3, DT3, AR4, DR4, SR4, RR4, SL4, TL4, KS4, ML4, DT4, SSGEG1, SSGEG2, SSGEG3, SSGEG4 }; BambooTracker-0.3.5/BambooTracker/instrument/instrument.cpp000066400000000000000000000345251362177441300241120ustar00rootroot00000000000000#include "instrument.hpp" #include AbstractInstrument::AbstractInstrument(int number, SoundSource source, std::string name, InstrumentsManager* owner) : owner_(owner), number_(number), name_(name), source_(source) {} int AbstractInstrument::getNumber() const { return number_; } void AbstractInstrument::setNumber(int n) { number_ = n; } SoundSource AbstractInstrument::getSoundSource() const { return source_; } std::string AbstractInstrument::getName() const { return name_; } void AbstractInstrument::setName(std::string name) { name_ = name; } bool AbstractInstrument::isRegisteredWithManager() const { return (this == owner_->getInstrumentSharedPtr(number_).get()); } /****************************************/ InstrumentFM::InstrumentFM(int number, std::string name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::FM, name, owner), envNum_(0), lfoEnabled_(false), lfoNum_(0) { opSeqEnabled_ = { { FMEnvelopeParameter::AL, false }, { FMEnvelopeParameter::FB, false }, { FMEnvelopeParameter::AR1, false }, { FMEnvelopeParameter::DR1, false }, { FMEnvelopeParameter::SR1, false }, { FMEnvelopeParameter::RR1, false }, { FMEnvelopeParameter::SL1, false }, { FMEnvelopeParameter::TL1, false }, { FMEnvelopeParameter::KS1, false }, { FMEnvelopeParameter::ML1, false }, { FMEnvelopeParameter::DT1, false }, { FMEnvelopeParameter::AR2, false }, { FMEnvelopeParameter::DR2, false }, { FMEnvelopeParameter::SR2, false }, { FMEnvelopeParameter::RR2, false }, { FMEnvelopeParameter::SL2, false }, { FMEnvelopeParameter::TL2, false }, { FMEnvelopeParameter::KS2, false }, { FMEnvelopeParameter::ML2, false }, { FMEnvelopeParameter::DT2, false }, { FMEnvelopeParameter::AR3, false }, { FMEnvelopeParameter::DR3, false }, { FMEnvelopeParameter::SR3, false }, { FMEnvelopeParameter::RR3, false }, { FMEnvelopeParameter::SL3, false }, { FMEnvelopeParameter::TL3, false }, { FMEnvelopeParameter::KS3, false }, { FMEnvelopeParameter::ML3, false }, { FMEnvelopeParameter::DT3, false }, { FMEnvelopeParameter::AR4, false }, { FMEnvelopeParameter::DR4, false }, { FMEnvelopeParameter::SR4, false }, { FMEnvelopeParameter::RR4, false }, { FMEnvelopeParameter::SL4, false }, { FMEnvelopeParameter::TL4, false }, { FMEnvelopeParameter::KS4, false }, { FMEnvelopeParameter::ML4, false }, { FMEnvelopeParameter::DT4, false } }; opSeqNum_ = { { FMEnvelopeParameter::AL, 0 }, { FMEnvelopeParameter::FB, 0 }, { FMEnvelopeParameter::AR1, 0 }, { FMEnvelopeParameter::DR1, 0 }, { FMEnvelopeParameter::SR1, 0 }, { FMEnvelopeParameter::RR1, 0 }, { FMEnvelopeParameter::SL1, 0 }, { FMEnvelopeParameter::TL1, 0 }, { FMEnvelopeParameter::KS1, 0 }, { FMEnvelopeParameter::ML1, 0 }, { FMEnvelopeParameter::DT1, 0 }, { FMEnvelopeParameter::AR2, 0 }, { FMEnvelopeParameter::DR2, 0 }, { FMEnvelopeParameter::SR2, 0 }, { FMEnvelopeParameter::RR2, 0 }, { FMEnvelopeParameter::SL2, 0 }, { FMEnvelopeParameter::TL2, 0 }, { FMEnvelopeParameter::KS2, 0 }, { FMEnvelopeParameter::ML2, 0 }, { FMEnvelopeParameter::DT2, 0 }, { FMEnvelopeParameter::AR3, 0 }, { FMEnvelopeParameter::DR3, 0 }, { FMEnvelopeParameter::SR3, 0 }, { FMEnvelopeParameter::RR3, 0 }, { FMEnvelopeParameter::SL3, 0 }, { FMEnvelopeParameter::TL3, 0 }, { FMEnvelopeParameter::KS3, 0 }, { FMEnvelopeParameter::ML3, 0 }, { FMEnvelopeParameter::DT3, 0 }, { FMEnvelopeParameter::AR4, 0 }, { FMEnvelopeParameter::DR4, 0 }, { FMEnvelopeParameter::SR4, 0 }, { FMEnvelopeParameter::RR4, 0 }, { FMEnvelopeParameter::SL4, 0 }, { FMEnvelopeParameter::TL4, 0 }, { FMEnvelopeParameter::KS4, 0 }, { FMEnvelopeParameter::ML4, 0 }, { FMEnvelopeParameter::DT4, 0 } }; arpEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; arpNum_ = { { FMOperatorType::All, 0 }, { FMOperatorType::Op1, 0 }, { FMOperatorType::Op2, 0 }, { FMOperatorType::Op3, 0 }, { FMOperatorType::Op4, 0 } }; ptEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; ptNum_ = { { FMOperatorType::All, 0 }, { FMOperatorType::Op1, 0 }, { FMOperatorType::Op2, 0 }, { FMOperatorType::Op3, 0 }, { FMOperatorType::Op4, 0 } }; envResetEnabled_ = { { FMOperatorType::All, false }, { FMOperatorType::Op1, false }, { FMOperatorType::Op2, false }, { FMOperatorType::Op3, false }, { FMOperatorType::Op4, false } }; } std::unique_ptr InstrumentFM::clone() { std::unique_ptr c = std::make_unique(number_, name_, owner_); c->setEnvelopeNumber(envNum_); c->setLFOEnabled(lfoEnabled_); c->setLFONumber(lfoNum_); for (auto pair : opSeqEnabled_) { c->setOperatorSequenceEnabled(pair.first, pair.second); c->setOperatorSequenceNumber(pair.first, opSeqNum_.at(pair.first)); } for (auto pair : arpEnabled_) { c->setArpeggioEnabled(pair.first, pair.second); c->setArpeggioNumber(pair.first, arpNum_.at(pair.first)); c->setPitchEnabled(pair.first, ptEnabled_.at(pair.first)); c->setPitchNumber(pair.first, ptNum_.at(pair.first)); c->setEnvelopeResetEnabled(pair.first, envResetEnabled_.at(pair.first)); } return std::move(c); } void InstrumentFM::setEnvelopeNumber(int n) { envNum_ = n; } int InstrumentFM::getEnvelopeNumber() const { return envNum_; } int InstrumentFM::getEnvelopeParameter(FMEnvelopeParameter param) const { return owner_->getEnvelopeFMParameter(envNum_, param); } bool InstrumentFM::getOperatorEnabled(int n) const { return owner_->getEnvelopeFMOperatorEnabled(envNum_, n); } void InstrumentFM::setLFOEnabled(bool enabled) { lfoEnabled_ = enabled; } bool InstrumentFM::getLFOEnabled() const { return lfoEnabled_; } void InstrumentFM::setLFONumber(int n) { lfoNum_ = n; } int InstrumentFM::getLFONumber() const { return lfoNum_; } int InstrumentFM::getLFOParameter(FMLFOParameter param) const { return owner_->getLFOFMparameter(lfoNum_, param); } void InstrumentFM::setEnvelopeResetEnabled(FMOperatorType op, bool enabled) { envResetEnabled_.at(op) = enabled; } bool InstrumentFM::getEnvelopeResetEnabled(FMOperatorType op) const { return envResetEnabled_.at(op); } void InstrumentFM::setOperatorSequenceEnabled(FMEnvelopeParameter param, bool enabled) { opSeqEnabled_.at(param) = enabled; } bool InstrumentFM::getOperatorSequenceEnabled(FMEnvelopeParameter param) const { return opSeqEnabled_.at(param); } void InstrumentFM::setOperatorSequenceNumber(FMEnvelopeParameter param, int n) { opSeqNum_.at(param) = n; } int InstrumentFM::getOperatorSequenceNumber(FMEnvelopeParameter param) const { return opSeqNum_.at(param); } std::vector InstrumentFM::getOperatorSequenceSequence(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMSequence(param, opSeqNum_.at(param)); } std::vector InstrumentFM::getOperatorSequenceLoops(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMLoops(param, opSeqNum_.at(param)); } Release InstrumentFM::getOperatorSequenceRelease(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMRelease(param, opSeqNum_.at(param)); } std::unique_ptr InstrumentFM::getOperatorSequenceSequenceIterator(FMEnvelopeParameter param) const { return owner_->getOperatorSequenceFMIterator(param, opSeqNum_.at(param)); } void InstrumentFM::setArpeggioEnabled(FMOperatorType op, bool enabled) { arpEnabled_.at(op) = enabled; } bool InstrumentFM::getArpeggioEnabled(FMOperatorType op) const { return arpEnabled_.at(op); } void InstrumentFM::setArpeggioNumber(FMOperatorType op, int n) { arpNum_.at(op) = n; } int InstrumentFM::getArpeggioNumber(FMOperatorType op) const { return arpNum_.at(op); } SequenceType InstrumentFM::getArpeggioType(FMOperatorType op) const { return owner_->getArpeggioFMType(arpNum_.at(op)); } std::vector InstrumentFM::getArpeggioSequence(FMOperatorType op) const { return owner_->getArpeggioFMSequence(arpNum_.at(op)); } std::vector InstrumentFM::getArpeggioLoops(FMOperatorType op) const { return owner_->getArpeggioFMLoops(arpNum_.at(op)); } Release InstrumentFM::getArpeggioRelease(FMOperatorType op) const { return owner_->getArpeggioFMRelease(arpNum_.at(op)); } std::unique_ptr InstrumentFM::getArpeggioSequenceIterator(FMOperatorType op) const { return owner_->getArpeggioFMIterator(arpNum_.at(op)); } void InstrumentFM::setPitchEnabled(FMOperatorType op, bool enabled) { ptEnabled_.at(op) = enabled; } bool InstrumentFM::getPitchEnabled(FMOperatorType op) const { return ptEnabled_.at(op); } void InstrumentFM::setPitchNumber(FMOperatorType op, int n) { ptNum_.at(op) = n; } int InstrumentFM::getPitchNumber(FMOperatorType op) const { return ptNum_.at(op); } SequenceType InstrumentFM::getPitchType(FMOperatorType op) const { return owner_->getPitchFMType(ptNum_.at(op)); } std::vector InstrumentFM::getPitchSequence(FMOperatorType op) const { return owner_->getPitchFMSequence(ptNum_.at(op)); } std::vector InstrumentFM::getPitchLoops(FMOperatorType op) const { return owner_->getPitchFMLoops(ptNum_.at(op)); } Release InstrumentFM::getPitchRelease(FMOperatorType op) const { return owner_->getPitchFMRelease(ptNum_.at(op)); } std::unique_ptr InstrumentFM::getPitchSequenceIterator(FMOperatorType op) const { return owner_->getPitchFMIterator(ptNum_.at(op)); } /****************************************/ InstrumentSSG::InstrumentSSG(int number, std::string name, InstrumentsManager* owner) : AbstractInstrument(number, SoundSource::SSG, name, owner), wfEnabled_(false), wfNum_(0), tnEnabled_(false), tnNum_(0), envEnabled_(false), envNum_(0), arpEnabled_(false), arpNum_(0), ptEnabled_(false), ptNum_(0) { } std::unique_ptr InstrumentSSG::clone() { std::unique_ptr c = std::make_unique(number_, name_, owner_); c->setWaveFormEnabled(wfEnabled_); c->setWaveFormNumber(wfNum_); c->setToneNoiseEnabled(tnEnabled_); c->setToneNoiseNumber(tnNum_); c->setEnvelopeEnabled(envEnabled_); c->setEnvelopeNumber(envNum_); c->setArpeggioEnabled(arpEnabled_); c->setArpeggioNumber(arpNum_); c->setPitchEnabled(ptEnabled_); c->setPitchNumber(ptNum_); return std::move(c); } void InstrumentSSG::setWaveFormEnabled(bool enabled) { wfEnabled_ = enabled; } bool InstrumentSSG::getWaveFormEnabled() const { return wfEnabled_; } void InstrumentSSG::setWaveFormNumber(int n) { wfNum_ = n; } int InstrumentSSG::getWaveFormNumber() const { return wfNum_; } std::vector InstrumentSSG::getWaveFormSequence() const { return owner_->getWaveFormSSGSequence(wfNum_); } std::vector InstrumentSSG::getWaveFormLoops() const { return owner_->getWaveFormSSGLoops(wfNum_); } Release InstrumentSSG::getWaveFormRelease() const { return owner_->getWaveFormSSGRelease(wfNum_); } std::unique_ptr InstrumentSSG::getWaveFormSequenceIterator() const { return owner_->getWaveFormSSGIterator(wfNum_); } void InstrumentSSG::setToneNoiseEnabled(bool enabled) { tnEnabled_ = enabled; } bool InstrumentSSG::getToneNoiseEnabled() const { return tnEnabled_; } void InstrumentSSG::setToneNoiseNumber(int n) { tnNum_ = n; } int InstrumentSSG::getToneNoiseNumber() const { return tnNum_; } std::vector InstrumentSSG::getToneNoiseSequence() const { return owner_->getToneNoiseSSGSequence(tnNum_); } std::vector InstrumentSSG::getToneNoiseLoops() const { return owner_->getToneNoiseSSGLoops(tnNum_); } Release InstrumentSSG::getToneNoiseRelease() const { return owner_->getToneNoiseSSGRelease(tnNum_); } std::unique_ptr InstrumentSSG::getToneNoiseSequenceIterator() const { return owner_->getToneNoiseSSGIterator(tnNum_); } void InstrumentSSG::setEnvelopeEnabled(bool enabled) { envEnabled_ = enabled; } bool InstrumentSSG::getEnvelopeEnabled() const { return envEnabled_; } void InstrumentSSG::setEnvelopeNumber(int n) { envNum_ = n; } int InstrumentSSG::getEnvelopeNumber() const { return envNum_; } std::vector InstrumentSSG::getEnvelopeSequence() const { return owner_->getEnvelopeSSGSequence(envNum_); } std::vector InstrumentSSG::getEnvelopeLoops() const { return owner_->getEnvelopeSSGLoops(envNum_); } Release InstrumentSSG::getEnvelopeRelease() const { return owner_->getEnvelopeSSGRelease(envNum_); } std::unique_ptr InstrumentSSG::getEnvelopeSequenceIterator() const { return owner_->getEnvelopeSSGIterator(envNum_); } void InstrumentSSG::setArpeggioEnabled(bool enabled) { arpEnabled_ = enabled; } bool InstrumentSSG::getArpeggioEnabled() const { return arpEnabled_; } void InstrumentSSG::setArpeggioNumber(int n) { arpNum_ = n; } int InstrumentSSG::getArpeggioNumber() const { return arpNum_; } SequenceType InstrumentSSG::getArpeggioType() const { return owner_->getArpeggioSSGType(arpNum_); } std::vector InstrumentSSG::getArpeggioSequence() const { return owner_->getArpeggioSSGSequence(arpNum_); } std::vector InstrumentSSG::getArpeggioLoops() const { return owner_->getArpeggioSSGLoops(arpNum_); } Release InstrumentSSG::getArpeggioRelease() const { return owner_->getArpeggioSSGRelease(arpNum_); } std::unique_ptr InstrumentSSG::getArpeggioSequenceIterator() const { return owner_->getArpeggioSSGIterator(arpNum_); } void InstrumentSSG::setPitchEnabled(bool enabled) { ptEnabled_ = enabled; } bool InstrumentSSG::getPitchEnabled() const { return ptEnabled_; } void InstrumentSSG::setPitchNumber(int n) { ptNum_ = n; } int InstrumentSSG::getPitchNumber() const { return ptNum_; } SequenceType InstrumentSSG::getPitchType() const { return owner_->getPitchSSGType(ptNum_); } std::vector InstrumentSSG::getPitchSequence() const { return owner_->getPitchSSGSequence(ptNum_); } std::vector InstrumentSSG::getPitchLoops() const { return owner_->getPitchSSGLoops(ptNum_); } Release InstrumentSSG::getPitchRelease() const { return owner_->getPitchSSGRelease(ptNum_); } std::unique_ptr InstrumentSSG::getPitchSequenceIterator() const { return owner_->getPitchSSGIterator(ptNum_); } BambooTracker-0.3.5/BambooTracker/instrument/instrument.hpp000066400000000000000000000133721362177441300241140ustar00rootroot00000000000000#pragma once #include #include #include #include #include "instruments_manager.hpp" #include "envelope_fm.hpp" #include "lfo_fm.hpp" #include "command_sequence.hpp" #include "enum_hash.hpp" #include "misc.hpp" class InstrumentsManager; class AbstractInstrument { public: virtual ~AbstractInstrument() = default; int getNumber() const; void setNumber(int n); SoundSource getSoundSource() const; std::string getName() const; void setName(std::string name); bool isRegisteredWithManager() const; virtual std::unique_ptr clone() = 0; protected: InstrumentsManager* owner_; int number_; std::string name_; // UTF-8 AbstractInstrument(int number, SoundSource source, std::string name, InstrumentsManager* owner); private: SoundSource source_; }; class InstrumentFM : public AbstractInstrument { public: InstrumentFM(int number, std::string name, InstrumentsManager* owner); std::unique_ptr clone() override; void setEnvelopeNumber(int n); int getEnvelopeNumber() const; int getEnvelopeParameter(FMEnvelopeParameter param) const; bool getOperatorEnabled(int n) const; void setLFOEnabled(bool enabled); bool getLFOEnabled() const; void setLFONumber(int n); int getLFONumber() const; int getLFOParameter(FMLFOParameter param) const; void setOperatorSequenceEnabled(FMEnvelopeParameter param, bool enabled); bool getOperatorSequenceEnabled(FMEnvelopeParameter param) const; void setOperatorSequenceNumber(FMEnvelopeParameter param, int n); int getOperatorSequenceNumber(FMEnvelopeParameter param) const; std::vector getOperatorSequenceSequence(FMEnvelopeParameter param) const; std::vector getOperatorSequenceLoops(FMEnvelopeParameter param) const; Release getOperatorSequenceRelease(FMEnvelopeParameter param) const; std::unique_ptr getOperatorSequenceSequenceIterator(FMEnvelopeParameter param) const; void setArpeggioEnabled(FMOperatorType op, bool enabled); bool getArpeggioEnabled(FMOperatorType op) const; void setArpeggioNumber(FMOperatorType op, int n); int getArpeggioNumber(FMOperatorType op) const; SequenceType getArpeggioType(FMOperatorType op) const; std::vector getArpeggioSequence(FMOperatorType op) const; std::vector getArpeggioLoops(FMOperatorType op) const; Release getArpeggioRelease(FMOperatorType op) const; std::unique_ptr getArpeggioSequenceIterator(FMOperatorType op) const; void setPitchEnabled(FMOperatorType op, bool enabled); bool getPitchEnabled(FMOperatorType op) const; void setPitchNumber(FMOperatorType op, int n); int getPitchNumber(FMOperatorType op) const; SequenceType getPitchType(FMOperatorType op) const; std::vector getPitchSequence(FMOperatorType op) const; std::vector getPitchLoops(FMOperatorType op) const; Release getPitchRelease(FMOperatorType op) const; std::unique_ptr getPitchSequenceIterator(FMOperatorType op) const; void setEnvelopeResetEnabled(FMOperatorType op, bool enabled); bool getEnvelopeResetEnabled(FMOperatorType op) const; private: int envNum_; bool lfoEnabled_; int lfoNum_; std::unordered_map opSeqEnabled_; std::unordered_map opSeqNum_; std::unordered_map arpEnabled_; std::unordered_map arpNum_; std::unordered_map ptEnabled_; std::unordered_map ptNum_; std::unordered_map envResetEnabled_; }; class InstrumentSSG : public AbstractInstrument { public: InstrumentSSG(int number, std::string name, InstrumentsManager* owner); std::unique_ptr clone() override; void setWaveFormEnabled(bool enabled); bool getWaveFormEnabled() const; void setWaveFormNumber(int n); int getWaveFormNumber() const; std::vector getWaveFormSequence() const; std::vector getWaveFormLoops() const; Release getWaveFormRelease() const; std::unique_ptr getWaveFormSequenceIterator() const; void setToneNoiseEnabled(bool enabled); bool getToneNoiseEnabled() const; void setToneNoiseNumber(int n); int getToneNoiseNumber() const; std::vector getToneNoiseSequence() const; std::vector getToneNoiseLoops() const; Release getToneNoiseRelease() const; std::unique_ptr getToneNoiseSequenceIterator() const; void setEnvelopeEnabled(bool enabled); bool getEnvelopeEnabled() const; void setEnvelopeNumber(int n); int getEnvelopeNumber() const; std::vector getEnvelopeSequence() const; std::vector getEnvelopeLoops() const; Release getEnvelopeRelease() const; std::unique_ptr getEnvelopeSequenceIterator() const; void setArpeggioEnabled(bool enabled); bool getArpeggioEnabled() const; void setArpeggioNumber(int n); int getArpeggioNumber() const; SequenceType getArpeggioType() const; std::vector getArpeggioSequence() const; std::vector getArpeggioLoops() const; Release getArpeggioRelease() const; std::unique_ptr getArpeggioSequenceIterator() const; void setPitchEnabled(bool enabled); bool getPitchEnabled() const; void setPitchNumber(int n); int getPitchNumber() const; SequenceType getPitchType() const; std::vector getPitchSequence() const; std::vector getPitchLoops() const; Release getPitchRelease() const; std::unique_ptr getPitchSequenceIterator() const; private: bool wfEnabled_; int wfNum_; bool tnEnabled_; int tnNum_; bool envEnabled_; int envNum_; bool arpEnabled_; int arpNum_; bool ptEnabled_; int ptNum_; }; BambooTracker-0.3.5/BambooTracker/instrument/instruments_manager.cpp000066400000000000000000001664461362177441300257770ustar00rootroot00000000000000#include "instruments_manager.hpp" #include #include #include #include "instrument.hpp" InstrumentsManager::InstrumentsManager() { envFMParams_ = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; fmOpTypes_ = { FMOperatorType::All, FMOperatorType::Op1, FMOperatorType::Op2, FMOperatorType::Op3, FMOperatorType::Op4 }; clearAll(); } void InstrumentsManager::addInstrument(int instNum, SoundSource source, std::string name) { if (instNum < 0 || static_cast(insts_.size()) <= instNum) return; switch (source) { case SoundSource::FM: { auto fm = std::make_shared(instNum, name, this); int envNum = findFirstFreePlainEnvelopeFM(); if (envNum == -1) envNum = static_cast(envFM_.size()) - 1; fm->setEnvelopeNumber(envNum); envFM_.at(static_cast(envNum))->registerUserInstrument(instNum); int lfoNum = findFirstFreePlainLFOFM(); if (lfoNum == -1) lfoNum = static_cast(lfoFM_.size()) - 1; fm->setLFONumber(lfoNum); fm->setLFOEnabled(false); for (auto param : envFMParams_) { int opSeqNum = findFirstFreePlainOperatorSequenceFM(param); if (opSeqNum == -1) opSeqNum = static_cast(opSeqFM_.at(param).size()) - 1; fm->setOperatorSequenceNumber(param, opSeqNum); fm->setOperatorSequenceEnabled(param, false); } int arpNum = findFirstFreePlainArpeggioFM(); if (arpNum == -1) arpNum = static_cast(arpFM_.size()) - 1; int ptNum = findFirstFreePlainPitchFM(); if (ptNum == -1) ptNum = static_cast(ptFM_.size()) - 1; for (auto type : fmOpTypes_) { fm->setArpeggioNumber(type, arpNum); fm->setArpeggioEnabled(type, false); fm->setPitchNumber(type, ptNum); fm->setPitchEnabled(type, false); } insts_.at(static_cast(instNum)) = std::move(fm); break; } case SoundSource::SSG: { auto ssg = std::make_shared(instNum, name, this); int wfNum = findFirstFreePlainWaveFormSSG(); if (wfNum == -1) wfNum = static_cast(wfSSG_.size()) - 1; ssg->setWaveFormNumber(wfNum); ssg->setWaveFormEnabled(false); int tnNum = findFirstFreePlainToneNoiseSSG(); if (tnNum == -1) tnNum = static_cast(tnSSG_.size()) - 1; ssg->setToneNoiseNumber(tnNum); ssg->setToneNoiseEnabled(false); int envNum = findFirstFreePlainEnvelopeSSG(); if (envNum == -1) envNum = static_cast(envSSG_.size()) - 1; ssg->setEnvelopeNumber(envNum); ssg->setEnvelopeEnabled(false); int arpNum = findFirstFreePlainArpeggioSSG(); if (arpNum == -1) arpNum = static_cast(arpSSG_.size()) - 1; ssg->setArpeggioNumber(arpNum); ssg->setArpeggioEnabled(false); int ptNum = findFirstFreePlainPitchSSG(); if (ptNum == -1) ptNum = static_cast(ptSSG_.size()) - 1; ssg->setPitchNumber(ptNum); ssg->setPitchEnabled(false); insts_.at(static_cast(instNum)) = std::move(ssg); break; } default: break; } } void InstrumentsManager::addInstrument(std::unique_ptr inst) { int num = inst->getNumber(); insts_.at(static_cast(num)) = std::move(inst); switch (insts_[static_cast(num)]->getSoundSource()) { case SoundSource::FM: { auto fm = std::dynamic_pointer_cast(insts_[static_cast(num)]); envFM_.at(static_cast(fm->getEnvelopeNumber()))->registerUserInstrument(num); if (fm->getLFOEnabled()) lfoFM_.at(static_cast(fm->getLFONumber()))->registerUserInstrument(num); for (auto p : envFMParams_) { if (fm->getOperatorSequenceEnabled(p)) opSeqFM_.at(p).at(static_cast(fm->getOperatorSequenceNumber(p))) ->registerUserInstrument(num); } for (auto t : fmOpTypes_) { if (fm->getArpeggioEnabled(t)) arpFM_.at(static_cast(fm->getArpeggioNumber(t)))->registerUserInstrument(num); if (fm->getPitchEnabled(t)) ptFM_.at(static_cast(fm->getPitchNumber(t)))->registerUserInstrument(num); } break; } case SoundSource::SSG: { auto ssg = std::dynamic_pointer_cast(insts_[static_cast(num)]); if (ssg->getWaveFormEnabled()) wfSSG_.at(static_cast(ssg->getWaveFormNumber()))->registerUserInstrument(num); if (ssg->getToneNoiseEnabled()) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->registerUserInstrument(num); if (ssg->getEnvelopeEnabled()) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->registerUserInstrument(num); if (ssg->getArpeggioEnabled()) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->registerUserInstrument(num); if (ssg->getPitchEnabled()) ptSSG_.at(static_cast(ssg->getPitchNumber()))->registerUserInstrument(num); break; } default: break; } } void InstrumentsManager::cloneInstrument(int cloneInstNum, int refInstNum) { std::shared_ptr refInst = insts_.at(static_cast(refInstNum)); addInstrument(cloneInstNum, refInst->getSoundSource(), refInst->getName()); switch (refInst->getSoundSource()) { case SoundSource::FM: { auto refFm = std::dynamic_pointer_cast(refInst); auto cloneFm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); setInstrumentFMEnvelope(cloneInstNum, refFm->getEnvelopeNumber()); setInstrumentFMLFO(cloneInstNum, refFm->getLFONumber()); if (refFm->getLFOEnabled()) setInstrumentFMLFOEnabled(cloneInstNum, true); for (auto p : envFMParams_) { setInstrumentFMOperatorSequence(cloneInstNum, p, refFm->getOperatorSequenceNumber(p)); if (refFm->getOperatorSequenceEnabled(p)) setInstrumentFMOperatorSequenceEnabled(cloneInstNum, p, true); } for (auto t : fmOpTypes_) { setInstrumentFMArpeggio(cloneInstNum, t, refFm->getArpeggioNumber(t)); if (refFm->getArpeggioEnabled(t)) setInstrumentFMArpeggioEnabled(cloneInstNum, t, true); setInstrumentFMPitch(cloneInstNum, t, refFm->getPitchNumber(t)); if (refFm->getPitchEnabled(t)) setInstrumentFMPitchEnabled(cloneInstNum, t, true); setInstrumentFMEnvelopeResetEnabled(cloneInstNum, t, refFm->getEnvelopeResetEnabled(t)); } break; } case SoundSource::SSG: { auto refSsg = std::dynamic_pointer_cast(refInst); auto cloneSsg = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); setInstrumentSSGWaveForm(cloneInstNum, refSsg->getWaveFormNumber()); if (refSsg->getWaveFormEnabled()) setInstrumentSSGWaveFormEnabled(cloneInstNum, true); setInstrumentSSGToneNoise(cloneInstNum, refSsg->getToneNoiseNumber()); if (refSsg->getToneNoiseEnabled()) setInstrumentSSGToneNoiseEnabled(cloneInstNum, true); setInstrumentSSGEnvelope(cloneInstNum, refSsg->getEnvelopeNumber()); if (refSsg->getEnvelopeEnabled()) setInstrumentSSGEnvelopeEnabled(cloneInstNum, true); setInstrumentSSGArpeggio(cloneInstNum, refSsg->getArpeggioNumber()); if (refSsg->getArpeggioEnabled()) setInstrumentSSGArpeggioEnabled(cloneInstNum, true); setInstrumentSSGPitch(cloneInstNum, refSsg->getPitchNumber()); if (refSsg->getPitchEnabled()) setInstrumentSSGPitchEnabled(cloneInstNum, true); break; } default: break; } } void InstrumentsManager::deepCloneInstrument(int cloneInstNum, int refInstNum) { std::shared_ptr refInst = insts_.at(static_cast(refInstNum)); addInstrument(cloneInstNum, refInst->getSoundSource(), refInst->getName()); switch (refInst->getSoundSource()) { case SoundSource::FM: { auto refFm = std::dynamic_pointer_cast(refInst); auto cloneFm = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); envFM_[static_cast(cloneFm->getEnvelopeNumber())]->deregisterUserInstrument(cloneInstNum); // Remove temporary number int envNum = cloneFMEnvelope(refFm->getEnvelopeNumber()); cloneFm->setEnvelopeNumber(envNum); envFM_[static_cast(envNum)]->registerUserInstrument(cloneInstNum); if (refFm->getLFOEnabled()) { cloneFm->setLFOEnabled(true); int lfoNum = cloneFMLFO(refFm->getLFONumber()); cloneFm->setLFONumber(lfoNum); lfoFM_[static_cast(lfoNum)]->registerUserInstrument(cloneInstNum); } for (auto p : envFMParams_) { if (refFm->getOperatorSequenceEnabled(p)) { cloneFm->setOperatorSequenceEnabled(p, true); int opSeqNum = cloneFMOperatorSequence(p, refFm->getOperatorSequenceNumber(p)); cloneFm->setOperatorSequenceNumber(p, opSeqNum); opSeqFM_.at(p)[static_cast(opSeqNum)]->registerUserInstrument(cloneInstNum); } } std::unordered_map arpNums; for (auto t : fmOpTypes_) { if (refFm->getArpeggioEnabled(t)) { cloneFm->setArpeggioEnabled(t, true); arpNums.emplace(refFm->getArpeggioNumber(t), -1); } } for (auto& pair : arpNums) pair.second = cloneFMArpeggio(pair.first); for (auto t : fmOpTypes_) { if (refFm->getArpeggioEnabled(t)) { int arpNum = arpNums.at(refFm->getArpeggioNumber(t)); cloneFm->setArpeggioNumber(t, arpNum); arpFM_[static_cast(arpNum)]->registerUserInstrument(cloneInstNum); } } std::unordered_map ptNums; for (auto t : fmOpTypes_) { if (refFm->getPitchEnabled(t)) { cloneFm->setPitchEnabled(t, true); ptNums.emplace(refFm->getPitchNumber(t), -1); } } for (auto& pair : ptNums) pair.second = cloneFMPitch(pair.first); for (auto t : fmOpTypes_) { if (refFm->getPitchEnabled(t)) { int ptNum = ptNums.at(refFm->getPitchNumber(t)); cloneFm->setPitchNumber(t, ptNum); ptFM_[static_cast(ptNum)]->registerUserInstrument(cloneInstNum); } } for (auto t : fmOpTypes_) setInstrumentFMEnvelopeResetEnabled(cloneInstNum, t, refFm->getEnvelopeResetEnabled(t)); break; } case SoundSource::SSG: { auto refSsg = std::dynamic_pointer_cast(refInst); auto cloneSsg = std::dynamic_pointer_cast(insts_.at(static_cast(cloneInstNum))); if (refSsg->getWaveFormEnabled()) { cloneSsg->setWaveFormEnabled(true); int wfNum = cloneSSGWaveForm(refSsg->getWaveFormNumber()); cloneSsg->setWaveFormNumber(wfNum); wfSSG_[static_cast(wfNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getToneNoiseEnabled()) { cloneSsg->setToneNoiseEnabled(true); int tnNum = cloneSSGToneNoise(refSsg->getToneNoiseNumber()); cloneSsg->setToneNoiseNumber(tnNum); tnSSG_[static_cast(tnNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getEnvelopeEnabled()) { cloneSsg->setEnvelopeEnabled(true); int envNum = cloneSSGEnvelope(refSsg->getEnvelopeNumber()); cloneSsg->setEnvelopeNumber(envNum); envSSG_[static_cast(envNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getArpeggioEnabled()) { cloneSsg->setArpeggioEnabled(true); int arpNum = cloneSSGArpeggio(refSsg->getArpeggioNumber()); cloneSsg->setArpeggioNumber(arpNum); arpSSG_[static_cast(arpNum)]->registerUserInstrument(cloneInstNum); } if (refSsg->getPitchEnabled()) { cloneSsg->setPitchEnabled(true); int ptNum = cloneSSGPitch(refSsg->getPitchNumber()); cloneSsg->setPitchNumber(ptNum); ptSSG_[static_cast(ptNum)]->registerUserInstrument(cloneInstNum); } break; } default: break; } } int InstrumentsManager::cloneFMEnvelope(int srcNum) { int cloneNum = 0; for (auto& env : envFM_) { if (!env->isUserInstrument()) { env = envFM_.at(static_cast(srcNum))->clone(); env->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneFMLFO(int srcNum) { int cloneNum = 0; for (auto& lfo : lfoFM_) { if (!lfo->isUserInstrument()) { lfo = lfoFM_.at(static_cast(srcNum))->clone(); lfo->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneFMOperatorSequence(FMEnvelopeParameter param, int srcNum) { int cloneNum = 0; for (auto& opSeq : opSeqFM_.at(param)) { if (!opSeq->isUserInstrument()) { opSeq = opSeqFM_.at(param).at(static_cast(srcNum))->clone(); opSeq->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneFMArpeggio(int srcNum) { int cloneNum = 0; for (auto& arp : arpFM_) { if (!arp->isUserInstrument()) { arp = arpFM_.at(static_cast(srcNum))->clone(); arp->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneFMPitch(int srcNum) { int cloneNum = 0; for (auto& pt : ptFM_) { if (!pt->isUserInstrument()) { pt = ptFM_.at(static_cast(srcNum))->clone(); pt->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneSSGWaveForm(int srcNum) { int cloneNum = 0; for (auto& wf : wfSSG_) { if (!wf->isUserInstrument()) { wf = wfSSG_.at(static_cast(srcNum))->clone(); wf->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneSSGToneNoise(int srcNum) { int cloneNum = 0; for (auto& tn : tnSSG_) { if (!tn->isUserInstrument()) { tn = tnSSG_.at(static_cast(srcNum))->clone(); tn->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneSSGEnvelope(int srcNum) { int cloneNum = 0; for (auto& env : envSSG_) { if (!env->isUserInstrument()) { env = envSSG_.at(static_cast(srcNum))->clone(); env->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneSSGArpeggio(int srcNum) { int cloneNum = 0; for (auto& arp : arpSSG_) { if (!arp->isUserInstrument()) { arp = arpSSG_.at(static_cast(srcNum))->clone(); arp->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } int InstrumentsManager::cloneSSGPitch(int srcNum) { int cloneNum = 0; for (auto& pt : ptSSG_) { if (!pt->isUserInstrument()) { pt = ptSSG_.at(static_cast(srcNum))->clone(); pt->setNumber(cloneNum); break; } ++cloneNum; } return cloneNum; } std::unique_ptr InstrumentsManager::removeInstrument(int instNum) { switch (insts_.at(static_cast(instNum))->getSoundSource()) { case SoundSource::FM: { auto fm = std::dynamic_pointer_cast(insts_[static_cast(instNum)]); envFM_.at(static_cast(fm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); if (fm->getLFOEnabled()) lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); for (auto p : envFMParams_) { if (fm->getOperatorSequenceEnabled(p)) opSeqFM_.at(p).at(static_cast(fm->getOperatorSequenceNumber(p))) ->deregisterUserInstrument(instNum); } for (auto t : fmOpTypes_) { if (fm->getArpeggioEnabled(t)) arpFM_.at(static_cast(fm->getArpeggioNumber(t)))->deregisterUserInstrument(instNum); if (fm->getPitchEnabled(t)) ptFM_.at(static_cast(fm->getPitchNumber(t)))->deregisterUserInstrument(instNum); } break; } case SoundSource::SSG: { auto ssg = std::dynamic_pointer_cast(insts_[static_cast(instNum)]); if (ssg->getWaveFormEnabled()) wfSSG_.at(static_cast(ssg->getWaveFormNumber()))->deregisterUserInstrument(instNum); if (ssg->getToneNoiseEnabled()) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); if (ssg->getEnvelopeEnabled()) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); if (ssg->getArpeggioEnabled()) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); if (ssg->getPitchEnabled()) ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); break; } default: break; } std::unique_ptr clone = insts_[static_cast(instNum)]->clone(); insts_[static_cast(instNum)].reset(); return clone; } std::shared_ptr InstrumentsManager::getInstrumentSharedPtr(int instNum) { if (0 <= instNum && instNum < static_cast(insts_.size()) && insts_.at(static_cast(instNum)) != nullptr) { return insts_[static_cast(instNum)]; } else { return std::shared_ptr(); // Return nullptr } } void InstrumentsManager::clearAll() { for (auto p : envFMParams_) { opSeqFM_.emplace(p, std::array, 128>()); } for (size_t i = 0; i < 128; ++i) { insts_[i].reset(); envFM_[i] = std::make_shared(i); lfoFM_[i] = std::make_shared(i); for (auto& p : opSeqFM_) { p.second[i] = std::make_shared(i); } arpFM_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 48); ptFM_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 127); wfSSG_[i] = std::make_shared(i); tnSSG_[i] = std::make_shared(i); envSSG_[i] = std::make_shared(i,SequenceType::NO_SEQUENCE_TYPE, 15); arpSSG_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 48); ptSSG_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 127); } } std::vector InstrumentsManager::getInstrumentIndices() const { std::vector idcs; for (size_t i = 0; i < insts_.size(); ++i) { if (insts_[i]) idcs.push_back(static_cast(i)); } return idcs; } void InstrumentsManager::setInstrumentName(int instNum, std::string name) { insts_.at(static_cast(instNum))->setName(name); } std::string InstrumentsManager::getInstrumentName(int instNum) const { return insts_.at(static_cast(instNum))->getName(); } std::vector InstrumentsManager::getInstrumentNameList() const { std::vector names; for (auto& inst : insts_) { if (inst) names.push_back(inst->getName()); } return names; } std::vector InstrumentsManager::getEntriedInstrumentIndices() const { std::vector idcs; int n = 0; for (auto& inst : insts_) { if (inst) idcs.push_back(n); ++n; } return idcs; } void InstrumentsManager::clearUnusedInstrumentProperties() { for (size_t i = 0; i < 128; ++i) { if (!envFM_[i]->isUserInstrument()) envFM_[i] = std::make_shared(i); if (!lfoFM_[i]->isUserInstrument()) lfoFM_[i] = std::make_shared(i); for (auto& p : opSeqFM_) { if (!p.second[i]->isUserInstrument()) p.second[i] = std::make_shared(i); } if (!arpFM_[i]->isUserInstrument()) arpFM_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 48); if (!ptFM_[i]->isUserInstrument()) ptFM_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 127); if (!wfSSG_[i]->isUserInstrument()) wfSSG_[i] = std::make_shared(i); if (!tnSSG_[i]->isUserInstrument()) tnSSG_[i] = std::make_shared(i); if (!envSSG_[i]->isUserInstrument()) envSSG_[i] = std::make_shared(i, SequenceType::NO_SEQUENCE_TYPE, 15); if (!arpSSG_[i]->isUserInstrument()) arpSSG_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 48); if (!ptSSG_[i]->isUserInstrument()) ptSSG_[i] = std::make_shared(i, SequenceType::ABSOLUTE_SEQUENCE, 127); } } /// Return: /// -1: no free instrument /// else: first free instrument number int InstrumentsManager::findFirstFreeInstrument() const { auto&& it = std::find_if_not(insts_.begin(), insts_.end(), [](const std::shared_ptr& inst) { return inst; }); return (it == insts_.end() ? -1 : std::distance(insts_.begin(), it)); } std::vector> InstrumentsManager::checkDuplicateInstruments() const { std::vector> dupList; std::vector idcs = getEntriedInstrumentIndices(); for (size_t i = 0; i < idcs.size(); ++i) { int baseIdx = idcs[i]; std::vector group { baseIdx }; std::shared_ptr base = insts_[baseIdx]; for (size_t j = i + 1; j < idcs.size();) { int tgtIdx = idcs[j]; std::shared_ptr tgt = insts_[tgtIdx]; if (base->getSoundSource() == tgt->getSoundSource()) { switch (base->getSoundSource()) { case SoundSource::FM: { if (equalPropertiesFM(std::dynamic_pointer_cast(base), std::dynamic_pointer_cast(tgt))) { group.push_back(tgtIdx); idcs.erase(idcs.begin() + j); continue; } break; } case SoundSource::SSG: { if (equalPropertiesSSG(std::dynamic_pointer_cast(base), std::dynamic_pointer_cast(tgt))) { group.push_back(tgtIdx); idcs.erase(idcs.begin() + j); continue; } break; } case SoundSource::DRUM: break; } } ++j; } if (group.size() > 1) dupList.push_back(group); } return dupList; } //----- FM methods ----- void InstrumentsManager::setInstrumentFMEnvelope(int instNum, int envNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); envFM_.at(static_cast(fm->getEnvelopeNumber()))->deregisterUserInstrument(instNum); envFM_.at(static_cast(envNum))->registerUserInstrument(instNum); fm->setEnvelopeNumber(envNum); } int InstrumentsManager::getInstrumentFMEnvelope(int instNum) const { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getEnvelopeNumber(); } void InstrumentsManager::setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value) { envFM_.at(static_cast(envNum))->setParameterValue(param, value); } int InstrumentsManager::getEnvelopeFMParameter(int envNum, FMEnvelopeParameter param) const { return envFM_.at(static_cast(envNum))->getParameterValue(param); } void InstrumentsManager::setEnvelopeFMOperatorEnabled(int envNum, int opNum, bool enabled) { envFM_.at(static_cast(envNum))->setOperatorEnabled(opNum, enabled); } bool InstrumentsManager::getEnvelopeFMOperatorEnabled(int envNum, int opNum) const { return envFM_.at(static_cast(envNum))->getOperatorEnabled(opNum); } std::vector InstrumentsManager::getEnvelopeFMUsers(int envNum) const { return envFM_.at(static_cast(envNum))->getUserInstruments(); } std::vector InstrumentsManager::getEnvelopeFMEntriedIndices() const { std::vector idcs; int n = 0; for (auto& env : envFM_) { if (env->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeEnvelopeFM() const { auto&& it = std::find_if_not(envFM_.begin(), envFM_.end(), [](const std::shared_ptr& env) { return env->isUserInstrument(); }); return (it == envFM_.end() ? -1 : std::distance(envFM_.begin(), it)); } int InstrumentsManager::findFirstFreePlainEnvelopeFM() const { auto&& it = std::find_if_not(envFM_.begin(), envFM_.end(), [](const std::shared_ptr& env) { return (env->isUserInstrument() || env->isEdited()); }); return (it == envFM_.end() ? -1 : std::distance(envFM_.begin(), it)); } void InstrumentsManager::setInstrumentFMLFOEnabled(int instNum, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setLFOEnabled(enabled); if (enabled) lfoFM_.at(static_cast(fm->getLFONumber()))->registerUserInstrument(instNum); else lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMLFOEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getLFOEnabled(); } void InstrumentsManager::setInstrumentFMLFO(int instNum, int lfoNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getLFOEnabled()) { lfoFM_.at(static_cast(fm->getLFONumber()))->deregisterUserInstrument(instNum); lfoFM_.at(static_cast(lfoNum))->registerUserInstrument(instNum); } fm->setLFONumber(lfoNum); } int InstrumentsManager::getInstrumentFMLFO(int instNum) const { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getLFONumber(); } void InstrumentsManager::setLFOFMParameter(int lfoNum, FMLFOParameter param, int value) { lfoFM_.at(static_cast(lfoNum))->setParameterValue(param, value); } int InstrumentsManager::getLFOFMparameter(int lfoNum, FMLFOParameter param) const { return lfoFM_.at(static_cast(lfoNum))->getParameterValue(param); } std::vector InstrumentsManager::getLFOFMUsers(int lfoNum) const { return lfoFM_.at(static_cast(lfoNum))->getUserInstruments(); } std::vector InstrumentsManager::getLFOFMEntriedIndices() const { std::vector idcs; int n = 0; for (auto& lfo : lfoFM_) { if (lfo->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeLFOFM() const { auto&& it = std::find_if_not(lfoFM_.begin(), lfoFM_.end(), [](const std::shared_ptr& lfo) { return lfo->isUserInstrument(); }); return (it == lfoFM_.end() ? -1 : std::distance(lfoFM_.begin(), it)); } int InstrumentsManager::findFirstFreePlainLFOFM() const { auto&& it = std::find_if_not(lfoFM_.begin(), lfoFM_.end(), [](const std::shared_ptr& lfo) { return (lfo->isUserInstrument() || lfo->isEdited()); }); return (it == lfoFM_.end() ? -1 : std::distance(lfoFM_.begin(), it)); } void InstrumentsManager::setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setOperatorSequenceEnabled(param, enabled); if (enabled) opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->registerUserInstrument(instNum); else opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getOperatorSequenceEnabled(param); } void InstrumentsManager::setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getOperatorSequenceEnabled(param)) { opSeqFM_.at(param).at(static_cast(fm->getOperatorSequenceNumber(param)))->deregisterUserInstrument(instNum); opSeqFM_.at(param).at(static_cast(opSeqNum))->registerUserInstrument(instNum); } fm->setOperatorSequenceNumber(param, opSeqNum); } int InstrumentsManager::getInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getOperatorSequenceNumber(param); } void InstrumentsManager::addOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int type, int data) { opSeqFM_.at(param).at(static_cast(opSeqNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum) { opSeqFM_.at(param).at(static_cast(opSeqNum))->removeSequenceCommand(); } void InstrumentsManager::setOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int cnt, int type, int data) { opSeqFM_.at(param).at(static_cast(opSeqNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getOperatorSequenceFMSequence(FMEnvelopeParameter param, int opSeqNum) { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getSequence(); } void InstrumentsManager::setOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum, std::vector begins, std::vector ends, std::vector times) { opSeqFM_.at(param).at(static_cast(opSeqNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getLoops(); } void InstrumentsManager::setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, ReleaseType type, int begin) { opSeqFM_.at(param).at(static_cast(opSeqNum))->setRelease(type, begin); } Release InstrumentsManager::getOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getRelease(); } std::unique_ptr InstrumentsManager::getOperatorSequenceFMIterator(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getIterator(); } std::vector InstrumentsManager::getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const { return opSeqFM_.at(param).at(static_cast(opSeqNum))->getUserInstruments(); } std::vector InstrumentsManager::getOperatorSequenceFMEntriedIndices(FMEnvelopeParameter param) const { std::vector idcs; int n = 0; for (auto& seq : opSeqFM_.at(param)) { if (seq->isUserInstrument() || seq->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeOperatorSequenceFM(FMEnvelopeParameter param) const { auto& opSeq = opSeqFM_.at(param); auto&& it = std::find_if_not(opSeq.begin(), opSeq.end(), [](const std::shared_ptr& seq) { return seq->isUserInstrument(); }); return (it == opSeq.end() ? -1 : std::distance(opSeq.begin(), it)); } int InstrumentsManager::findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter param) const { auto& opSeq = opSeqFM_.at(param); auto&& it = std::find_if_not(opSeq.begin(), opSeq.end(), [](const std::shared_ptr& seq) { return (seq->isUserInstrument() || seq->isEdited()); }); return (it == opSeq.end() ? -1 : std::distance(opSeq.begin(), it)); } void InstrumentsManager::setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setArpeggioEnabled(op, enabled); if (enabled) arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->registerUserInstrument(instNum); else arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioEnabled(op); } void InstrumentsManager::setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getArpeggioEnabled(op)) { arpFM_.at(static_cast(fm->getArpeggioNumber(op)))->deregisterUserInstrument(instNum); arpFM_.at(static_cast(arpNum))->registerUserInstrument(instNum); } fm->setArpeggioNumber(op, arpNum); } int InstrumentsManager::getInstrumentFMArpeggio(int instNum, FMOperatorType op) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getArpeggioNumber(op); } void InstrumentsManager::setArpeggioFMType(int arpNum, SequenceType type) { arpFM_.at(static_cast(arpNum))->setType(type); } SequenceType InstrumentsManager::getArpeggioFMType(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getType(); } void InstrumentsManager::addArpeggioFMSequenceCommand(int arpNum, int type, int data) { arpFM_.at(static_cast(arpNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeArpeggioFMSequenceCommand(int arpNum) { arpFM_.at(static_cast(arpNum))->removeSequenceCommand(); } void InstrumentsManager::setArpeggioFMSequenceCommand(int arpNum, int cnt, int type, int data) { arpFM_.at(static_cast(arpNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getArpeggioFMSequence(int arpNum) { return arpFM_.at(static_cast(arpNum))->getSequence(); } void InstrumentsManager::setArpeggioFMLoops(int arpNum, std::vector begins, std::vector ends, std::vector times) { arpFM_.at(static_cast(arpNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getArpeggioFMLoops(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getLoops(); } void InstrumentsManager::setArpeggioFMRelease(int arpNum, ReleaseType type, int begin) { arpFM_.at(static_cast(arpNum))->setRelease(type, begin); } Release InstrumentsManager::getArpeggioFMRelease(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getRelease(); } std::unique_ptr InstrumentsManager::getArpeggioFMIterator(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getIterator(); } std::vector InstrumentsManager::getArpeggioFMUsers(int arpNum) const { return arpFM_.at(static_cast(arpNum))->getUserInstruments(); } std::vector InstrumentsManager::getArpeggioFMEntriedIndices() const { std::vector idcs; int n = 0; for (auto& arp : arpFM_) { if (arp->isUserInstrument() || arp->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeArpeggioFM() const { auto&& it = std::find_if_not(arpFM_.begin(), arpFM_.end(), [](const std::shared_ptr& arp) { return arp->isUserInstrument(); }); return (it == arpFM_.end() ? -1 : std::distance(arpFM_.begin(), it)); } int InstrumentsManager::findFirstFreePlainArpeggioFM() const { auto&& it = std::find_if_not(arpFM_.begin(), arpFM_.end(), [](const std::shared_ptr& arp) { return (arp->isUserInstrument() || arp->isEdited()); }); return (it == arpFM_.end() ? -1 : std::distance(arpFM_.begin(), it)); } void InstrumentsManager::setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); fm->setPitchEnabled(op, enabled); if (enabled) ptFM_.at(static_cast(fm->getPitchNumber(op)))->registerUserInstrument(instNum); else ptFM_.at(static_cast(fm->getPitchNumber(op)))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentFMPitchEnabled(int instNum, FMOperatorType op) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchEnabled(op); } void InstrumentsManager::setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum) { auto fm = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (fm->getPitchEnabled(op)) { ptFM_.at(static_cast(fm->getPitchNumber(op)))->deregisterUserInstrument(instNum); ptFM_.at(static_cast(ptNum))->registerUserInstrument(instNum); } fm->setPitchNumber(op, ptNum); } int InstrumentsManager::getInstrumentFMPitch(int instNum, FMOperatorType op) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getPitchNumber(op); } void InstrumentsManager::setPitchFMType(int ptNum, SequenceType type) { ptFM_.at(static_cast(ptNum))->setType(type); } SequenceType InstrumentsManager::getPitchFMType(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getType(); } void InstrumentsManager::addPitchFMSequenceCommand(int ptNum, int type, int data) { ptFM_.at(static_cast(ptNum))->addSequenceCommand(type, data); } void InstrumentsManager::removePitchFMSequenceCommand(int ptNum) { ptFM_.at(static_cast(ptNum))->removeSequenceCommand(); } void InstrumentsManager::setPitchFMSequenceCommand(int ptNum, int cnt, int type, int data) { ptFM_.at(static_cast(ptNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getPitchFMSequence(int ptNum) { return ptFM_.at(static_cast(ptNum))->getSequence(); } void InstrumentsManager::setPitchFMLoops(int ptNum, std::vector begins, std::vector ends, std::vector times) { ptFM_.at(static_cast(ptNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getPitchFMLoops(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getLoops(); } void InstrumentsManager::setPitchFMRelease(int ptNum, ReleaseType type, int begin) { ptFM_.at(static_cast(ptNum))->setRelease(type, begin); } Release InstrumentsManager::getPitchFMRelease(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getRelease(); } std::unique_ptr InstrumentsManager::getPitchFMIterator(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getIterator(); } std::vector InstrumentsManager::getPitchFMUsers(int ptNum) const { return ptFM_.at(static_cast(ptNum))->getUserInstruments(); } void InstrumentsManager::setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled) { std::dynamic_pointer_cast(insts_[static_cast(instNum)])->setEnvelopeResetEnabled(op, enabled); } std::vector InstrumentsManager::getPitchFMEntriedIndices() const { std::vector idcs; int n = 0; for (auto& pt : ptFM_) { if (pt->isUserInstrument() || pt->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreePitchFM() const { auto&& it = std::find_if_not(ptFM_.begin(), ptFM_.end(), [](const std::shared_ptr& pt) { return pt->isUserInstrument(); }); return (it == ptFM_.end() ? -1 : std::distance(ptFM_.begin(), it)); } int InstrumentsManager::findFirstFreePlainPitchFM() const { auto&& it = std::find_if_not(ptFM_.begin(), ptFM_.end(), [](const std::shared_ptr& pt) { return (pt->isUserInstrument() || pt->isEdited()); }); return (it == ptFM_.end() ? -1 : std::distance(ptFM_.begin(), it)); } bool InstrumentsManager::equalPropertiesFM(std::shared_ptr a, std::shared_ptr b) const { if (*envFM_[a->getEnvelopeNumber()].get() != *envFM_[b->getEnvelopeNumber()].get()) return false; if (a->getLFOEnabled() != b->getLFOEnabled()) return false; if (a->getLFOEnabled() && *lfoFM_[a->getLFONumber()].get() != *lfoFM_[b->getLFONumber()].get()) return false; for (auto& pair : opSeqFM_) { if (a->getOperatorSequenceEnabled(pair.first) != b->getOperatorSequenceEnabled(pair.first)) return false; if (a->getOperatorSequenceEnabled(pair.first) && *pair.second[a->getOperatorSequenceNumber(pair.first)].get() != *pair.second[b->getOperatorSequenceNumber(pair.first)].get()) return false; } for (auto& type : fmOpTypes_) { if (a->getArpeggioEnabled(type) != b->getArpeggioEnabled(type)) return false; if (a->getArpeggioEnabled(type) && *arpFM_[a->getArpeggioNumber(type)].get() != *arpFM_[b->getArpeggioNumber(type)].get()) return false; if (a->getPitchEnabled(type) != b->getPitchEnabled(type)) return false; if (a->getPitchEnabled(type) && *ptFM_[a->getPitchNumber(type)].get() != *ptFM_[b->getPitchNumber(type)].get()) return false; if (a->getEnvelopeResetEnabled(type) != b->getEnvelopeResetEnabled(type)) return false; } return true; } //----- SSG methods ----- void InstrumentsManager::setInstrumentSSGWaveFormEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setWaveFormEnabled(enabled); if (enabled) wfSSG_.at(static_cast(ssg->getWaveFormNumber()))->registerUserInstrument(instNum); else wfSSG_.at(static_cast(ssg->getWaveFormNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGWaveFormEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getWaveFormEnabled(); } void InstrumentsManager::setInstrumentSSGWaveForm(int instNum, int wfNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getWaveFormEnabled()) { wfSSG_.at(static_cast(ssg->getWaveFormNumber()))->deregisterUserInstrument(instNum); wfSSG_.at(static_cast(wfNum))->registerUserInstrument(instNum); } ssg->setWaveFormNumber(wfNum); } int InstrumentsManager::getInstrumentSSGWaveForm(int instNum) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getWaveFormNumber(); } void InstrumentsManager::addWaveFormSSGSequenceCommand(int wfNum, int type, int data) { wfSSG_.at(static_cast(wfNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeWaveFormSSGSequenceCommand(int wfNum) { wfSSG_.at(static_cast(wfNum))->removeSequenceCommand(); } void InstrumentsManager::setWaveFormSSGSequenceCommand(int wfNum, int cnt, int type, int data) { wfSSG_.at(static_cast(wfNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getWaveFormSSGSequence(int wfNum) { return wfSSG_.at(static_cast(wfNum))->getSequence(); } void InstrumentsManager::setWaveFormSSGLoops(int wfNum, std::vector begins, std::vector ends, std::vector times) { wfSSG_.at(static_cast(wfNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getWaveFormSSGLoops(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getLoops(); } void InstrumentsManager::setWaveFormSSGRelease(int wfNum, ReleaseType type, int begin) { wfSSG_.at(static_cast(wfNum))->setRelease(type, begin); } Release InstrumentsManager::getWaveFormSSGRelease(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getRelease(); } std::unique_ptr InstrumentsManager::getWaveFormSSGIterator(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getIterator(); } std::vector InstrumentsManager::getWaveFormSSGUsers(int wfNum) const { return wfSSG_.at(static_cast(wfNum))->getUserInstruments(); } std::vector InstrumentsManager::getWaveFormSSGEntriedIndices() const { std::vector idcs; int n = 0; for (auto& wf : wfSSG_) { if (wf->isUserInstrument() || wf->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeWaveFormSSG() const { auto&& it = std::find_if_not(wfSSG_.begin(), wfSSG_.end(), [](const std::shared_ptr& wf) { return wf->isUserInstrument(); }); return (it == wfSSG_.end() ? -1 : std::distance(wfSSG_.begin(), it)); } int InstrumentsManager::findFirstFreePlainWaveFormSSG() const { auto&& it = std::find_if_not(wfSSG_.begin(), wfSSG_.end(), [](const std::shared_ptr& wf) { return (wf->isUserInstrument() || wf->isEdited()); }); return (it == wfSSG_.end() ? -1 : std::distance(wfSSG_.begin(), it)); } void InstrumentsManager::setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setToneNoiseEnabled(enabled); if (enabled) tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->registerUserInstrument(instNum); else tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGToneNoiseEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getToneNoiseEnabled(); } void InstrumentsManager::setInstrumentSSGToneNoise(int instNum, int tnNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getToneNoiseEnabled()) { tnSSG_.at(static_cast(ssg->getToneNoiseNumber()))->deregisterUserInstrument(instNum); tnSSG_.at(static_cast(tnNum))->registerUserInstrument(instNum); } ssg->setToneNoiseNumber(tnNum); } int InstrumentsManager::getInstrumentSSGToneNoise(int instNum) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getToneNoiseNumber(); } void InstrumentsManager::addToneNoiseSSGSequenceCommand(int tnNum, int type, int data) { tnSSG_.at(static_cast(tnNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeToneNoiseSSGSequenceCommand(int tnNum) { tnSSG_.at(static_cast(tnNum))->removeSequenceCommand(); } void InstrumentsManager::setToneNoiseSSGSequenceCommand(int tnNum, int cnt, int type, int data) { tnSSG_.at(static_cast(tnNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getToneNoiseSSGSequence(int tnNum) { return tnSSG_.at(static_cast(tnNum))->getSequence(); } void InstrumentsManager::setToneNoiseSSGLoops(int tnNum, std::vector begins, std::vector ends, std::vector times) { tnSSG_.at(static_cast(tnNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getToneNoiseSSGLoops(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getLoops(); } void InstrumentsManager::setToneNoiseSSGRelease(int tnNum, ReleaseType type, int begin) { tnSSG_.at(static_cast(tnNum))->setRelease(type, begin); } Release InstrumentsManager::getToneNoiseSSGRelease(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getRelease(); } std::unique_ptr InstrumentsManager::getToneNoiseSSGIterator(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getIterator(); } std::vector InstrumentsManager::getToneNoiseSSGUsers(int tnNum) const { return tnSSG_.at(static_cast(tnNum))->getUserInstruments(); } std::vector InstrumentsManager::getToneNoiseSSGEntriedIndices() const { std::vector idcs; int n = 0; for (auto& tn : tnSSG_) { if (tn->isUserInstrument() || tn->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeToneNoiseSSG() const { auto&& it = std::find_if_not(tnSSG_.begin(), tnSSG_.end(), [](const std::shared_ptr& tn) { return tn->isUserInstrument(); }); return (it == tnSSG_.end() ? -1 : std::distance(tnSSG_.begin(), it)); } int InstrumentsManager::findFirstFreePlainToneNoiseSSG() const { auto&& it = std::find_if_not(tnSSG_.begin(), tnSSG_.end(), [](const std::shared_ptr& tn) { return (tn->isUserInstrument() || tn->isEdited()); }); return (it == tnSSG_.end() ? -1 : std::distance(tnSSG_.begin(), it)); } void InstrumentsManager::setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setEnvelopeEnabled(enabled); if (enabled) envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->registerUserInstrument(instNum); else envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGEnvelopeEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getEnvelopeEnabled(); } void InstrumentsManager::setInstrumentSSGEnvelope(int instNum, int envNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getEnvelopeEnabled()) { envSSG_.at(static_cast(ssg->getEnvelopeNumber()))->deregisterUserInstrument(instNum); envSSG_.at(static_cast(envNum))->registerUserInstrument(instNum); } ssg->setEnvelopeNumber(envNum); } int InstrumentsManager::getInstrumentSSGEnvelope(int instNum) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getEnvelopeNumber(); } void InstrumentsManager::addEnvelopeSSGSequenceCommand(int envNum, int type, int data) { envSSG_.at(static_cast(envNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeEnvelopeSSGSequenceCommand(int envNum) { envSSG_.at(static_cast(envNum))->removeSequenceCommand(); } void InstrumentsManager::setEnvelopeSSGSequenceCommand(int envNum, int cnt, int type, int data) { envSSG_.at(static_cast(envNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getEnvelopeSSGSequence(int envNum) { return envSSG_.at(static_cast(envNum))->getSequence(); } void InstrumentsManager::setEnvelopeSSGLoops(int envNum, std::vector begins, std::vector ends, std::vector times) { envSSG_.at(static_cast(envNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getEnvelopeSSGLoops(int envNum) const { return envSSG_.at(static_cast(envNum))->getLoops(); } void InstrumentsManager::setEnvelopeSSGRelease(int envNum, ReleaseType type, int begin) { envSSG_.at(static_cast(envNum))->setRelease(type, begin); } Release InstrumentsManager::getEnvelopeSSGRelease(int envNum) const { return envSSG_.at(static_cast(envNum))->getRelease(); } std::unique_ptr InstrumentsManager::getEnvelopeSSGIterator(int envNum) const { return envSSG_.at(static_cast(envNum))->getIterator(); } std::vector InstrumentsManager::getEnvelopeSSGUsers(int envNum) const { return envSSG_.at(static_cast(envNum))->getUserInstruments(); } std::vector InstrumentsManager::getEnvelopeSSGEntriedIndices() const { std::vector idcs; int n = 0; for (auto& env : envSSG_) { if (env->isUserInstrument() || env->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeEnvelopeSSG() const { auto&& it = std::find_if_not(envSSG_.begin(), envSSG_.end(), [](const std::shared_ptr& env) { return env->isUserInstrument(); }); return (it == envSSG_.end() ? -1 : std::distance(envSSG_.begin(), it)); } int InstrumentsManager::findFirstFreePlainEnvelopeSSG() const { auto&& it = std::find_if_not(envSSG_.begin(), envSSG_.end(), [](const std::shared_ptr& env) { return (env->isUserInstrument() || env->isEdited()); }); return (it == envSSG_.end() ? -1 : std::distance(envSSG_.begin(), it)); } void InstrumentsManager::setInstrumentSSGArpeggioEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setArpeggioEnabled(enabled); if (enabled) arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->registerUserInstrument(instNum); else arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGArpeggioEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getArpeggioEnabled(); } void InstrumentsManager::setInstrumentSSGArpeggio(int instNum, int arpNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getArpeggioEnabled()) { arpSSG_.at(static_cast(ssg->getArpeggioNumber()))->deregisterUserInstrument(instNum); arpSSG_.at(static_cast(arpNum))->registerUserInstrument(instNum); } ssg->setArpeggioNumber(arpNum); } int InstrumentsManager::getInstrumentSSGArpeggio(int instNum) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getArpeggioNumber(); } void InstrumentsManager::setArpeggioSSGType(int arpNum, SequenceType type) { arpSSG_.at(static_cast(arpNum))->setType(type); } SequenceType InstrumentsManager::getArpeggioSSGType(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getType(); } void InstrumentsManager::addArpeggioSSGSequenceCommand(int arpNum, int type, int data) { arpSSG_.at(static_cast(arpNum))->addSequenceCommand(type, data); } void InstrumentsManager::removeArpeggioSSGSequenceCommand(int arpNum) { arpSSG_.at(static_cast(arpNum))->removeSequenceCommand(); } void InstrumentsManager::setArpeggioSSGSequenceCommand(int arpNum, int cnt, int type, int data) { arpSSG_.at(static_cast(arpNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getArpeggioSSGSequence(int arpNum) { return arpSSG_.at(static_cast(arpNum))->getSequence(); } void InstrumentsManager::setArpeggioSSGLoops(int arpNum, std::vector begins, std::vector ends, std::vector times) { arpSSG_.at(static_cast(arpNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getArpeggioSSGLoops(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getLoops(); } void InstrumentsManager::setArpeggioSSGRelease(int arpNum, ReleaseType type, int begin) { arpSSG_.at(static_cast(arpNum))->setRelease(type, begin); } Release InstrumentsManager::getArpeggioSSGRelease(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getRelease(); } std::unique_ptr InstrumentsManager::getArpeggioSSGIterator(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getIterator(); } std::vector InstrumentsManager::getArpeggioSSGUsers(int arpNum) const { return arpSSG_.at(static_cast(arpNum))->getUserInstruments(); } std::vector InstrumentsManager::getArpeggioSSGEntriedIndices() const { std::vector idcs; int n = 0; for (auto& arp : arpSSG_) { if (arp->isUserInstrument() || arp->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreeArpeggioSSG() const { auto&& it = std::find_if_not(arpSSG_.begin(), arpSSG_.end(), [](const std::shared_ptr& arp) { return arp->isUserInstrument(); }); return (it == arpSSG_.end() ? -1 : std::distance(arpSSG_.begin(), it)); } int InstrumentsManager::findFirstFreePlainArpeggioSSG() const { auto&& it = std::find_if_not(arpSSG_.begin(), arpSSG_.end(), [](const std::shared_ptr& arp) { return (arp->isUserInstrument() || arp->isEdited()); }); return (it == arpSSG_.end() ? -1 : std::distance(arpSSG_.begin(), it)); } void InstrumentsManager::setInstrumentSSGPitchEnabled(int instNum, bool enabled) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); ssg->setPitchEnabled(enabled); if (enabled) ptSSG_.at(static_cast(ssg->getPitchNumber()))->registerUserInstrument(instNum); else ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); } bool InstrumentsManager::getInstrumentSSGPitchEnabled(int instNum) const { return std::dynamic_pointer_cast(insts_.at(static_cast(instNum)))->getPitchEnabled(); } void InstrumentsManager::setInstrumentSSGPitch(int instNum, int ptNum) { auto ssg = std::dynamic_pointer_cast(insts_.at(static_cast(instNum))); if (ssg->getPitchEnabled()) { ptSSG_.at(static_cast(ssg->getPitchNumber()))->deregisterUserInstrument(instNum); ptSSG_.at(static_cast(ptNum))->registerUserInstrument(instNum); } ssg->setPitchNumber(ptNum); } int InstrumentsManager::getInstrumentSSGPitch(int instNum) { return std::dynamic_pointer_cast(insts_[static_cast(instNum)])->getPitchNumber(); } void InstrumentsManager::setPitchSSGType(int ptNum, SequenceType type) { ptSSG_.at(static_cast(ptNum))->setType(type); } SequenceType InstrumentsManager::getPitchSSGType(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getType(); } void InstrumentsManager::addPitchSSGSequenceCommand(int ptNum, int type, int data) { ptSSG_.at(static_cast(ptNum))->addSequenceCommand(type, data); } void InstrumentsManager::removePitchSSGSequenceCommand(int ptNum) { ptSSG_.at(static_cast(ptNum))->removeSequenceCommand(); } void InstrumentsManager::setPitchSSGSequenceCommand(int ptNum, int cnt, int type, int data) { ptSSG_.at(static_cast(ptNum))->setSequenceCommand(cnt, type, data); } std::vector InstrumentsManager::getPitchSSGSequence(int ptNum) { return ptSSG_.at(static_cast(ptNum))->getSequence(); } void InstrumentsManager::setPitchSSGLoops(int ptNum, std::vector begins, std::vector ends, std::vector times) { ptSSG_.at(static_cast(ptNum))->setLoops(std::move(begins), std::move(ends), std::move(times)); } std::vector InstrumentsManager::getPitchSSGLoops(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getLoops(); } void InstrumentsManager::setPitchSSGRelease(int ptNum, ReleaseType type, int begin) { ptSSG_.at(static_cast(ptNum))->setRelease(type, begin); } Release InstrumentsManager::getPitchSSGRelease(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getRelease(); } std::unique_ptr InstrumentsManager::getPitchSSGIterator(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getIterator(); } std::vector InstrumentsManager::getPitchSSGUsers(int ptNum) const { return ptSSG_.at(static_cast(ptNum))->getUserInstruments(); } std::vector InstrumentsManager::getPitchSSGEntriedIndices() const { std::vector idcs; int n = 0; for (auto& pt : ptSSG_) { if (pt->isUserInstrument() || pt->isEdited()) idcs.push_back(n); ++n; } return idcs; } int InstrumentsManager::findFirstFreePitchSSG() const { auto&& it = std::find_if_not(ptSSG_.begin(), ptSSG_.end(), [](const std::shared_ptr& pt) { return pt->isUserInstrument(); }); return (it == ptSSG_.end() ? -1 : std::distance(ptSSG_.begin(), it)); } int InstrumentsManager::findFirstFreePlainPitchSSG() const { auto&& it = std::find_if_not(ptSSG_.begin(), ptSSG_.end(), [](const std::shared_ptr& pt) { return (pt->isUserInstrument() || pt->isEdited()); }); return (it == ptSSG_.end() ? -1 : std::distance(ptSSG_.begin(), it)); } bool InstrumentsManager::equalPropertiesSSG(std::shared_ptr a, std::shared_ptr b) const { if (a->getWaveFormEnabled() != b->getWaveFormEnabled()) return false; if (a->getWaveFormEnabled() && *wfSSG_[a->getWaveFormNumber()].get() != *wfSSG_[b->getWaveFormNumber()].get()) return false; if (a->getToneNoiseEnabled() != b->getToneNoiseEnabled()) return false; if (a->getToneNoiseEnabled() && *tnSSG_[a->getToneNoiseNumber()].get() != *tnSSG_[b->getToneNoiseNumber()].get()) return false; if (a->getEnvelopeEnabled() != b->getEnvelopeEnabled()) return false; if (a->getEnvelopeEnabled() && *envSSG_[a->getEnvelopeNumber()].get() != *envSSG_[b->getEnvelopeNumber()].get()) return false; if (a->getArpeggioEnabled() != b->getArpeggioEnabled()) return false; if (a->getArpeggioEnabled() && *arpSSG_[a->getArpeggioNumber()].get() != *arpSSG_[b->getArpeggioNumber()].get()) return false; if (a->getPitchEnabled() != b->getPitchEnabled()) return false; if (a->getPitchEnabled() && *ptSSG_[a->getPitchNumber()].get() != *ptSSG_[b->getPitchNumber()].get()) return false; return true; } BambooTracker-0.3.5/BambooTracker/instrument/instruments_manager.hpp000066400000000000000000000321761362177441300257740ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "instrument.hpp" #include "envelope_fm.hpp" #include "lfo_fm.hpp" #include "command_sequence.hpp" #include "enum_hash.hpp" #include "misc.hpp" class AbstractInstrument; class InstrumentFM; enum class FMEnvelopeParameter; class EnvelopeFM; class InstrumentSSG; class InstrumentsManager { public: InstrumentsManager(); void addInstrument(int instNum, SoundSource source, std::string name); void addInstrument(std::unique_ptr inst); std::unique_ptr removeInstrument(int instNum); void cloneInstrument(int cloneInstNum, int resInstNum); void deepCloneInstrument(int cloneInstNum, int resInstNum); std::shared_ptr getInstrumentSharedPtr(int instNum); void clearAll(); std::vector getInstrumentIndices() const; void setInstrumentName(int instNum, std::string name); std::string getInstrumentName(int instNum) const; std::vector getInstrumentNameList() const; std::vector getEntriedInstrumentIndices() const; void clearUnusedInstrumentProperties(); int findFirstFreeInstrument() const; std::vector> checkDuplicateInstruments() const; private: std::array, 128> insts_; //----- FM methods ----- public: void setInstrumentFMEnvelope(int instNum, int envNum); int getInstrumentFMEnvelope(int instNum) const; void setEnvelopeFMParameter(int envNum, FMEnvelopeParameter param, int value); int getEnvelopeFMParameter(int envNum, FMEnvelopeParameter param) const; void setEnvelopeFMOperatorEnabled(int envNum, int opNum, bool enabled); bool getEnvelopeFMOperatorEnabled(int envNum, int opNum) const; std::vector getEnvelopeFMUsers(int envNum) const; std::vector getEnvelopeFMEntriedIndices() const; int findFirstFreeEnvelopeFM() const; int findFirstFreePlainEnvelopeFM() const; void setInstrumentFMLFOEnabled(int instNum, bool enabled); bool getInstrumentFMLFOEnabled(int instNum) const; void setInstrumentFMLFO(int instNum, int lfoNum); int getInstrumentFMLFO(int instNum) const; void setLFOFMParameter(int lfoNum, FMLFOParameter param, int value); int getLFOFMparameter(int lfoNum, FMLFOParameter param) const; std::vector getLFOFMUsers(int lfoNum) const; std::vector getLFOFMEntriedIndices() const; int findFirstFreeLFOFM() const; int findFirstFreePlainLFOFM() const; void setInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param, bool enabled); bool getInstrumentFMOperatorSequenceEnabled(int instNum, FMEnvelopeParameter param) const; void setInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param, int opSeqNum); int getInstrumentFMOperatorSequence(int instNum, FMEnvelopeParameter param); void addOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int type, int data); void removeOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMSequenceCommand(FMEnvelopeParameter param, int opSeqNum, int cnt, int type, int data); std::vector getOperatorSequenceFMSequence(FMEnvelopeParameter param, int opSeqNum); void setOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum, std::vector begins, std::vector ends, std::vector times); std::vector getOperatorSequenceFMLoops(FMEnvelopeParameter param, int opSeqNum) const; void setOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum, ReleaseType type, int begin); Release getOperatorSequenceFMRelease(FMEnvelopeParameter param, int opSeqNum) const; std::unique_ptr getOperatorSequenceFMIterator(FMEnvelopeParameter param, int opSeqNum) const; std::vector getOperatorSequenceFMUsers(FMEnvelopeParameter param, int opSeqNum) const; std::vector getOperatorSequenceFMEntriedIndices(FMEnvelopeParameter param) const; int findFirstFreeOperatorSequenceFM(FMEnvelopeParameter param) const; int findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter param) const; void setInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op, bool enabled); bool getInstrumentFMArpeggioEnabled(int instNum, FMOperatorType op) const; void setInstrumentFMArpeggio(int instNum, FMOperatorType op, int arpNum); int getInstrumentFMArpeggio(int instNum, FMOperatorType op); void setArpeggioFMType(int arpNum, SequenceType type); SequenceType getArpeggioFMType(int arpNum) const; void addArpeggioFMSequenceCommand(int arpNum, int type, int data); void removeArpeggioFMSequenceCommand(int arpNum); void setArpeggioFMSequenceCommand(int arpNum, int cnt, int type, int data); std::vector getArpeggioFMSequence(int arpNum); void setArpeggioFMLoops(int arpNum, std::vector begins, std::vector ends, std::vector times); std::vector getArpeggioFMLoops(int arpNum) const; void setArpeggioFMRelease(int arpNum, ReleaseType type, int begin); Release getArpeggioFMRelease(int arpNum) const; std::unique_ptr getArpeggioFMIterator(int arpNum) const; std::vector getArpeggioFMUsers(int arpNum) const; std::vector getArpeggioFMEntriedIndices() const; int findFirstFreeArpeggioFM() const; int findFirstFreePlainArpeggioFM() const; void setInstrumentFMPitchEnabled(int instNum, FMOperatorType op, bool enabled); bool getInstrumentFMPitchEnabled(int instNum, FMOperatorType op) const; void setInstrumentFMPitch(int instNum, FMOperatorType op, int ptNum); int getInstrumentFMPitch(int instNum, FMOperatorType op); void setPitchFMType(int ptNum, SequenceType type); SequenceType getPitchFMType(int ptNum) const; void addPitchFMSequenceCommand(int ptNum, int type, int data); void removePitchFMSequenceCommand(int ptNum); void setPitchFMSequenceCommand(int ptNum, int cnt, int type, int data); std::vector getPitchFMSequence(int ptNum); void setPitchFMLoops(int ptNum, std::vector begins, std::vector ends, std::vector times); std::vector getPitchFMLoops(int ptNum) const; void setPitchFMRelease(int ptNum, ReleaseType type, int begin); Release getPitchFMRelease(int ptNum) const; std::unique_ptr getPitchFMIterator(int ptNum) const; std::vector getPitchFMUsers(int ptNum) const; std::vector getPitchFMEntriedIndices() const; int findFirstFreePitchFM() const; int findFirstFreePlainPitchFM() const; void setInstrumentFMEnvelopeResetEnabled(int instNum, FMOperatorType op, bool enabled); private: std::array, 128> envFM_; std::array, 128> lfoFM_; std::unordered_map, 128>> opSeqFM_; std::array, 128> arpFM_; std::array, 128> ptFM_; std::vector envFMParams_; std::vector fmOpTypes_; int cloneFMEnvelope(int srcNum); int cloneFMLFO(int srcNum); int cloneFMOperatorSequence(FMEnvelopeParameter param, int srcNum); int cloneFMArpeggio(int srcNum); int cloneFMPitch(int srcNum); bool equalPropertiesFM(std::shared_ptr a, std::shared_ptr b) const; //----- SSG methods ----- public: void setInstrumentSSGWaveFormEnabled(int instNum, bool enabled); bool getInstrumentSSGWaveFormEnabled(int instNum) const; void setInstrumentSSGWaveForm(int instNum, int wfNum); int getInstrumentSSGWaveForm(int instNum); void addWaveFormSSGSequenceCommand(int wfNum, int type, int data); void removeWaveFormSSGSequenceCommand(int wfNum); void setWaveFormSSGSequenceCommand(int wfNum, int cnt, int type, int data); std::vector getWaveFormSSGSequence(int wfNum); void setWaveFormSSGLoops(int wfNum, std::vector begins, std::vector ends, std::vector times); std::vector getWaveFormSSGLoops(int wfNum) const; void setWaveFormSSGRelease(int wfNum, ReleaseType type, int begin); Release getWaveFormSSGRelease(int wfNum) const; std::unique_ptr getWaveFormSSGIterator(int wfNum) const; std::vector getWaveFormSSGUsers(int wfNum) const; std::vector getWaveFormSSGEntriedIndices() const; int findFirstFreeWaveFormSSG() const; int findFirstFreePlainWaveFormSSG() const; void setInstrumentSSGToneNoiseEnabled(int instNum, bool enabled); bool getInstrumentSSGToneNoiseEnabled(int instNum) const; void setInstrumentSSGToneNoise(int instNum, int tnNum); int getInstrumentSSGToneNoise(int instNum); void addToneNoiseSSGSequenceCommand(int tnNum, int type, int data); void removeToneNoiseSSGSequenceCommand(int tnNum); void setToneNoiseSSGSequenceCommand(int tnNum, int cnt, int type, int data); std::vector getToneNoiseSSGSequence(int tnNum); void setToneNoiseSSGLoops(int tnNum, std::vector begins, std::vector ends, std::vector times); std::vector getToneNoiseSSGLoops(int tnNum) const; void setToneNoiseSSGRelease(int tnNum, ReleaseType type, int begin); Release getToneNoiseSSGRelease(int tnNum) const; std::unique_ptr getToneNoiseSSGIterator(int tnNum) const; std::vector getToneNoiseSSGUsers(int tnNum) const; std::vector getToneNoiseSSGEntriedIndices() const; int findFirstFreeToneNoiseSSG() const; int findFirstFreePlainToneNoiseSSG() const; void setInstrumentSSGEnvelopeEnabled(int instNum, bool enabled); bool getInstrumentSSGEnvelopeEnabled(int instNum) const; void setInstrumentSSGEnvelope(int instNum, int envNum); int getInstrumentSSGEnvelope(int instNum); void addEnvelopeSSGSequenceCommand(int envNum, int type, int data); void removeEnvelopeSSGSequenceCommand(int envNum); void setEnvelopeSSGSequenceCommand(int envNum, int cnt, int type, int data); std::vector getEnvelopeSSGSequence(int envNum); void setEnvelopeSSGLoops(int envNum, std::vector begins, std::vector ends, std::vector times); std::vector getEnvelopeSSGLoops(int envNum) const; void setEnvelopeSSGRelease(int envNum, ReleaseType type, int begin); Release getEnvelopeSSGRelease(int envNum) const; std::unique_ptr getEnvelopeSSGIterator(int envNum) const; std::vector getEnvelopeSSGUsers(int envNum) const; std::vector getEnvelopeSSGEntriedIndices() const; int findFirstFreeEnvelopeSSG() const; int findFirstFreePlainEnvelopeSSG() const; void setInstrumentSSGArpeggioEnabled(int instNum, bool enabled); bool getInstrumentSSGArpeggioEnabled(int instNum) const; void setInstrumentSSGArpeggio(int instNum, int arpNum); int getInstrumentSSGArpeggio(int instNum); void setArpeggioSSGType(int arpNum, SequenceType type); SequenceType getArpeggioSSGType(int arpNum) const; void addArpeggioSSGSequenceCommand(int arpNum, int type, int data); void removeArpeggioSSGSequenceCommand(int arpNum); void setArpeggioSSGSequenceCommand(int arpNum, int cnt, int type, int data); std::vector getArpeggioSSGSequence(int arpNum); void setArpeggioSSGLoops(int arpNum, std::vector begins, std::vector ends, std::vector times); std::vector getArpeggioSSGLoops(int arpNum) const; void setArpeggioSSGRelease(int arpNum, ReleaseType type, int begin); Release getArpeggioSSGRelease(int arpNum) const; std::unique_ptr getArpeggioSSGIterator(int arpNum) const; std::vector getArpeggioSSGUsers(int arpNum) const; std::vector getArpeggioSSGEntriedIndices() const; int findFirstFreeArpeggioSSG() const; int findFirstFreePlainArpeggioSSG() const; void setInstrumentSSGPitchEnabled(int instNum, bool enabled); bool getInstrumentSSGPitchEnabled(int instNum) const; void setInstrumentSSGPitch(int instNum, int ptNum); int getInstrumentSSGPitch(int instNum); void setPitchSSGType(int ptNum, SequenceType type); SequenceType getPitchSSGType(int ptNum) const; void addPitchSSGSequenceCommand(int ptNum, int type, int data); void removePitchSSGSequenceCommand(int ptNum); void setPitchSSGSequenceCommand(int ptNum, int cnt, int type, int data); std::vector getPitchSSGSequence(int ptNum); void setPitchSSGLoops(int ptNum, std::vector begins, std::vector ends, std::vector times); std::vector getPitchSSGLoops(int ptNum) const; void setPitchSSGRelease(int ptNum, ReleaseType type, int begin); Release getPitchSSGRelease(int ptNum) const; std::unique_ptr getPitchSSGIterator(int ptNum) const; std::vector getPitchSSGUsers(int ptNum) const; std::vector getPitchSSGEntriedIndices() const; int findFirstFreePitchSSG() const; int findFirstFreePlainPitchSSG() const; private: std::array, 128> wfSSG_; std::array, 128> envSSG_; std::array, 128> tnSSG_; std::array, 128> arpSSG_; std::array, 128> ptSSG_; int cloneSSGWaveForm(int srcNum); int cloneSSGToneNoise(int srcNum); int cloneSSGEnvelope(int srcNum); int cloneSSGArpeggio(int srcNum); int cloneSSGPitch(int srcNum); bool equalPropertiesSSG(std::shared_ptr a, std::shared_ptr b) const; }; BambooTracker-0.3.5/BambooTracker/instrument/lfo_fm.cpp000066400000000000000000000041111362177441300231300ustar00rootroot00000000000000#include "lfo_fm.hpp" #include constexpr int LFOFM::DEF_AM_OP[4]; LFOFM::LFOFM(int n) : AbstractInstrumentProperty (n), freq_(DEF_FREQ), pms_(DEF_PMS), ams_(DEF_AMS), cnt_(DEF_CNT) { for (int i = 0; i < 4; ++i) amOp_[i] = DEF_AM_OP[i]; } LFOFM::LFOFM(const LFOFM& other) : AbstractInstrumentProperty (other) { freq_ = other.freq_; ams_ = other.ams_; pms_ = other.pms_; cnt_ = other.cnt_; for (int i = 0; i < 4; ++i) amOp_[i] = other.amOp_[i]; } bool operator==(const LFOFM& a, const LFOFM& b) { return (a.freq_ == b.freq_ && a.ams_ == b.ams_ && a.pms_ == b.pms_ && a.cnt_ == b.cnt_ && a.amOp_[0] == b.amOp_[0] && a.amOp_[1] == b.amOp_[1] && a.amOp_[2] == b.amOp_[2] && a.amOp_[3] == b.amOp_[3]); } std::unique_ptr LFOFM::clone() { std::unique_ptr clone = std::make_unique(*this); clone->clearUserInstruments(); return clone; } void LFOFM::setParameterValue(FMLFOParameter param, int value) { switch (param) { case FMLFOParameter::FREQ: freq_ = value; break; case FMLFOParameter::PMS: pms_ = value; break; case FMLFOParameter::AMS: ams_ = value; break; case FMLFOParameter::Count: cnt_ = value; break; case FMLFOParameter::AM1: amOp_[0] = value; break; case FMLFOParameter::AM2: amOp_[1] = value; break; case FMLFOParameter::AM3: amOp_[2] = value; break; case FMLFOParameter::AM4: amOp_[3] = value; break; } } int LFOFM::getParameterValue(FMLFOParameter param) const { switch (param) { case FMLFOParameter::FREQ: return freq_; case FMLFOParameter::PMS: return pms_; case FMLFOParameter::AMS: return ams_; case FMLFOParameter::Count: return cnt_; case FMLFOParameter::AM1: return amOp_[0]; case FMLFOParameter::AM2: return amOp_[1]; case FMLFOParameter::AM3: return amOp_[2]; case FMLFOParameter::AM4: return amOp_[3]; default: throw std::invalid_argument("Unexpected FMLFOParameter."); } } bool LFOFM::isEdited() const { if (freq_ != DEF_FREQ || pms_ != DEF_PMS || ams_ != DEF_AMS || cnt_ != DEF_CNT) return true; for (int i = 0; i < 4; ++i) { if (amOp_[i] != DEF_AM_OP[i]) return true; } return false; } BambooTracker-0.3.5/BambooTracker/instrument/lfo_fm.hpp000066400000000000000000000015421362177441300231420ustar00rootroot00000000000000#pragma once #include #include "abstract_instrument_property.hpp" enum class FMLFOParameter; class LFOFM : public AbstractInstrumentProperty { public: explicit LFOFM(int n); LFOFM(const LFOFM& other); friend bool operator==(const LFOFM& a, const LFOFM& b); friend bool operator!=(const LFOFM& a, const LFOFM& b) { return !(a == b); } std::unique_ptr clone(); void setParameterValue(FMLFOParameter param, int value); int getParameterValue(FMLFOParameter param) const; bool isEdited() const; private: int freq_; int pms_; int ams_; int amOp_[4]; int cnt_; static constexpr int DEF_FREQ = 0; static constexpr int DEF_PMS = 0; static constexpr int DEF_AMS = 0; static constexpr int DEF_AM_OP[4] = { 0, 0, 0, 0 }; static constexpr int DEF_CNT = 0; }; enum class FMLFOParameter { FREQ, AMS, PMS, Count, AM1, AM2, AM3, AM4 }; BambooTracker-0.3.5/BambooTracker/instrument/sequence_iterator_interface.hpp000066400000000000000000000007361362177441300274450ustar00rootroot00000000000000#pragma once class SequenceIteratorInterface { public: virtual ~SequenceIteratorInterface() = default; /// -1: sequence end /// else: position in the sequence virtual int getPosition() const = 0; /// 0: absolute /// 1: fixed /// 2: relative virtual int getSequenceType() const = 0; virtual int getCommandType() const = 0; virtual int getCommandData() const = 0; virtual int next(bool isReleaseBegin = false) = 0; virtual int front() = 0; virtual int end() = 0; }; BambooTracker-0.3.5/BambooTracker/io/000077500000000000000000000000001362177441300173645ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/io/bank_io.cpp000066400000000000000000000743661362177441300215120ustar00rootroot00000000000000#include "bank_io.hpp" #include #include #include "file_io.hpp" #include "file_io_error.hpp" #include "version.hpp" BankIO::BankIO() {} void BankIO::saveBank(BinaryContainer& ctr, std::vector instNums, std::weak_ptr instMan) { ctr.appendString("BambooTrackerBnk"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofBankFileInBCD(); ctr.appendUint32(fileVersion); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset ctr.appendUint8(static_cast(instNums.size())); for (auto& idx : instNums) { if (std::shared_ptr inst = instMan.lock()->getInstrumentSharedPtr(static_cast(idx))) { ctr.appendUint8(static_cast(idx)); size_t iOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument block offset std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getSoundSource()) { case SoundSource::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instFM->getEnvelopeNumber())); uint8_t tmp = static_cast(instFM->getLFONumber()); ctr.appendUint8(instFM->getLFOEnabled() ? tmp : (0x80 | tmp)); for (auto& param : FileIO::ENV_FM_PARAMS) { tmp = static_cast(instFM->getOperatorSequenceNumber(param)); ctr.appendUint8(instFM->getOperatorSequenceEnabled(param) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getArpeggioNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getArpeggioEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getPitchNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getPitchEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); for (auto& type : FileIO::OP_FM_TYPES) { tmp = static_cast(instFM->getArpeggioNumber(type)); ctr.appendUint8(instFM->getArpeggioEnabled(type) ? tmp : (0x80 | tmp)); } for (auto& type : FileIO::OP_FM_TYPES) { tmp = static_cast(instFM->getPitchNumber(type)); ctr.appendUint8(instFM->getPitchEnabled(type) ? tmp : (0x80 | tmp)); } break; } case SoundSource::SSG: { ctr.appendUint8(0x01); auto instSSG = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instSSG->getWaveFormNumber()); ctr.appendUint8(instSSG->getWaveFormEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getToneNoiseNumber()); ctr.appendUint8(instSSG->getToneNoiseEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getEnvelopeNumber()); ctr.appendUint8(instSSG->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getArpeggioNumber()); ctr.appendUint8(instSSG->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getPitchNumber()); ctr.appendUint8(instSSG->getPitchEnabled() ? tmp : (0x80 | tmp)); break; } case SoundSource::DRUM: break; } ctr.writeUint32(iOfs, ctr.size() - iOfs); } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset // FM envelope std::vector envFMIdcs; for (auto& idx : instMan.lock()->getEnvelopeFMEntriedIndices()) { std::vector users = instMan.lock()->getEnvelopeFMUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) envFMIdcs.push_back(idx); } if (!envFMIdcs.empty()) { ctr.appendUint8(0x00); ctr.appendUint8(static_cast(envFMIdcs.size())); for (auto& idx : envFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AL) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); // Operator 1 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 0); tmp = static_cast((tmp << 5)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL1) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL1)); ctr.appendUint8(tmp); int tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG1); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML1)); ctr.appendUint8(tmp); // Operator 2 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 1); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL2) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL2)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG2); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML2)); ctr.appendUint8(tmp); // Operator 3 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 2); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL3) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL3)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG3); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML3)); ctr.appendUint8(tmp); // Operator 4 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 3); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL4) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL4)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG4); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML4)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM LFO std::vector lfoFMIdcs; for (auto& idx : instMan.lock()->getLFOFMEntriedIndices()) { std::vector users = instMan.lock()->getLFOFMUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) lfoFMIdcs.push_back(idx); } if (!lfoFMIdcs.empty()) { ctr.appendUint8(0x01); ctr.appendUint8(static_cast(lfoFMIdcs.size())); for (auto& idx : lfoFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::FREQ) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM4) << 7) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM3) << 6) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM2) << 5) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM1) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { std::vector idcs; for (auto& idx : instMan.lock()->getOperatorSequenceFMEntriedIndices(FileIO::ENV_FM_PARAMS[i])) { std::vector users = instMan.lock()->getOperatorSequenceFMUsers(FileIO::ENV_FM_PARAMS[i], idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) idcs.push_back(idx); } if (!idcs.empty()) { ctr.appendUint8(0x02 + static_cast(i)); ctr.appendUint8(static_cast(idcs.size())); for (auto& idx : idcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getOperatorSequenceFMSequence(FileIO::ENV_FM_PARAMS[i], idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getOperatorSequenceFMLoops(FileIO::ENV_FM_PARAMS[i], idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getOperatorSequenceFMRelease(FileIO::ENV_FM_PARAMS[i], idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } } // FM arpeggio std::vector arpFMIdcs; for (auto& idx : instMan.lock()->getArpeggioFMEntriedIndices()) { std::vector users = instMan.lock()->getArpeggioFMUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) arpFMIdcs.push_back(idx); } if (!arpFMIdcs.empty()) { ctr.appendUint8(0x28); ctr.appendUint8(static_cast(arpFMIdcs.size())); for (auto& idx : arpFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioFMLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioFMRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioFMType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pitch std::vector ptFMIdcs; for (auto& idx : instMan.lock()->getPitchFMEntriedIndices()) { std::vector users = instMan.lock()->getPitchFMUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) ptFMIdcs.push_back(idx); } if (!ptFMIdcs.empty()) { ctr.appendUint8(0x29); ctr.appendUint8(static_cast(ptFMIdcs.size())); for (auto& idx : ptFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchFMLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchFMRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchFMType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG wave form std::vector wfSSGIdcs; for (auto& idx : instMan.lock()->getWaveFormSSGEntriedIndices()) { std::vector users = instMan.lock()->getWaveFormSSGUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) wfSSGIdcs.push_back(idx); } if (!wfSSGIdcs.empty()) { ctr.appendUint8(0x30); ctr.appendUint8(static_cast(wfSSGIdcs.size())); for (auto& idx : wfSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getWaveFormSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getWaveFormSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getWaveFormSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG tone/noise std::vector tnSSGIdcs; for (auto& idx : instMan.lock()->getToneNoiseSSGEntriedIndices()) { std::vector users = instMan.lock()->getToneNoiseSSGUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) tnSSGIdcs.push_back(idx); } if (!tnSSGIdcs.empty()) { ctr.appendUint8(0x31); ctr.appendUint8(static_cast(tnSSGIdcs.size())); for (auto& idx : tnSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getToneNoiseSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getToneNoiseSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getToneNoiseSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG envelope std::vector envSSGIdcs; for (auto& idx : instMan.lock()->getEnvelopeSSGEntriedIndices()) { std::vector users = instMan.lock()->getEnvelopeSSGUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) envSSGIdcs.push_back(idx); } if (!envSSGIdcs.empty()) { ctr.appendUint8(0x32); ctr.appendUint8(static_cast(envSSGIdcs.size())); for (auto& idx : envSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getEnvelopeSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getEnvelopeSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getEnvelopeSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG arpeggio std::vector arpSSGIdcs; for (auto& idx : instMan.lock()->getArpeggioSSGEntriedIndices()) { std::vector users = instMan.lock()->getArpeggioSSGUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) arpSSGIdcs.push_back(idx); } if (!arpSSGIdcs.empty()) { ctr.appendUint8(0x33); ctr.appendUint8(static_cast(arpSSGIdcs.size())); for (auto& idx : arpSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioSSGType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG pitch std::vector ptSSGIdcs; for (auto& idx : instMan.lock()->getPitchSSGEntriedIndices()) { std::vector users = instMan.lock()->getPitchSSGUsers(idx); std::vector intersection; std::set_intersection(users.begin(), users.end(), instNums.begin(), instNums.end(), std::back_inserter(intersection)); if (!intersection.empty()) ptSSGIdcs.push_back(idx); } if (!ptSSGIdcs.empty()) { ctr.appendUint8(0x34); ctr.appendUint8(static_cast(ptSSGIdcs.size())); for (auto& idx : ptSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchSSGType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } AbstractBank* BankIO::loadBank(BinaryContainer& ctr, std::string path) { std::string ext = FileIO::getExtension(path); if (ext.compare("wopn") == 0) return BankIO::loadWOPNFile(ctr); if (ext.compare("btb") == 0) return BankIO::loadBTBFile(ctr); throw FileInputError(FileIO::FileType::Bank); } AbstractBank* BankIO::loadBTBFile(BinaryContainer& ctr) { size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerBnk") throw FileCorruptionError(FileIO::FileType::Bank); globCsr += 16; /*size_t eofOfs = */ctr.readUint32(globCsr); globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofBankFileInBCD()) throw FileVersionError(FileIO::FileType::Bank); globCsr += 4; /***** Instrument section *****/ std::vector ids; std::vector names; std::vector instCtrs; if (ctr.readString(globCsr, 8) != "INSTRMNT") throw FileCorruptionError(FileIO::FileType::Bank); globCsr += 8; size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; uint8_t instCnt = ctr.readUint8(instCsr); instCsr += 1; for (uint8_t i = 0; i < instCnt; ++i) { size_t pos = instCsr; uint8_t idx = ctr.readUint8(instCsr); ids.push_back(idx); instCsr += 1; size_t iOfs = ctr.readUint32(instCsr); size_t iCsr = instCsr + 4; size_t nameLen = ctr.readUint32(iCsr); iCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(iCsr, nameLen); iCsr += nameLen; } names.push_back(name); instCsr += iOfs; // Jump to next instCtrs.push_back(ctr.getSubcontainer(pos, 1 + iOfs)); } globCsr += instOfs; /***** Instrument property section *****/ if (ctr.readString(globCsr, 8) != "INSTPROP") throw FileCorruptionError(FileIO::FileType::Inst); globCsr += 8; size_t instPropOfs = ctr.readUint32(globCsr); BinaryContainer propCtr = ctr.getSubcontainer(globCsr + 4, instPropOfs - 4); return new BtBank(std::move(ids), std::move(names), std::move(instCtrs), std::move(propCtr), fileVersion); } AbstractBank* BankIO::loadWOPNFile(BinaryContainer& ctr) { struct WOPNDeleter { void operator()(WOPNFile *x) { WOPN_Free(x); } }; std::unique_ptr wopn; wopn.reset(WOPN_LoadBankFromMem(const_cast(ctr.getPointer()), ctr.size(), nullptr)); if (!wopn) throw FileCorruptionError(FileIO::FileType::Bank); WopnBank *bank = new WopnBank(wopn.get()); wopn.release(); return bank; } BambooTracker-0.3.5/BambooTracker/io/bank_io.hpp000066400000000000000000000010211362177441300214710ustar00rootroot00000000000000#pragma once #include #include #include #include "bank.hpp" #include "module.hpp" #include "binary_container.hpp" #include "format/wopn_file.h" class BankIO { public: static void saveBank(BinaryContainer& ctr, std::vector instNums, std::weak_ptr instMan); static AbstractBank* loadBank(BinaryContainer& ctr, std::string path); static AbstractBank* loadBTBFile(BinaryContainer& ctr); static AbstractBank* loadWOPNFile(BinaryContainer& ctr); private: BankIO(); }; BambooTracker-0.3.5/BambooTracker/io/binary_container.cpp000066400000000000000000000134611362177441300234230ustar00rootroot00000000000000#include "binary_container.hpp" #include #include #include BinaryContainer::BinaryContainer(size_t defCapacity) : isLE_(true) { if (defCapacity) reserve(defCapacity); } BinaryContainer::BinaryContainer(std::vector buf) : buf_(buf), isLE_(true) { } size_t BinaryContainer::size() const { return buf_.size(); } void BinaryContainer::clear() { buf_.clear(); buf_.shrink_to_fit(); } void BinaryContainer::reserve(size_t capacity) { buf_.reserve(capacity); } void BinaryContainer::setEndian(bool isLittleEndian) { isLE_ = isLittleEndian; } bool BinaryContainer::isLittleEndian() const { return isLE_; } void BinaryContainer::appendInt8(const int8_t v) { buf_.push_back(static_cast(v)); } void BinaryContainer::appendUint8(const uint8_t v) { buf_.push_back(static_cast(v)); } void BinaryContainer::appendInt16(const int16_t v) { append({ static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::appendUint16(const uint16_t v) { append({ static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::appendInt32(const int32_t v) { append({ static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::appendUint32(const uint32_t v) { append({ static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::appendChar(const char c) { buf_.push_back(c); } void BinaryContainer::appendString(const std::string str) { std::copy(str.begin(), str.end(), std::back_inserter(buf_)); } void BinaryContainer::appendArray(const uint8_t* array, size_t size) { std::copy(array, array + size, std::back_inserter(buf_)); } void BinaryContainer::appendVector(const std::vector vec) { std::copy(vec.begin(), vec.end(), std::back_inserter(buf_)); } void BinaryContainer::appendVector(const std::vector vec) { std::copy(vec.begin(), vec.end(), std::back_inserter(buf_)); } void BinaryContainer::writeInt8(size_t offset, const int8_t v) { buf_.at(offset) = static_cast(v); } void BinaryContainer::writeUint8(size_t offset, const uint8_t v) { buf_.at(offset) = static_cast(v); } void BinaryContainer::writeInt16(size_t offset, const int16_t v) { write(offset, { static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::writeUint16(size_t offset, const uint16_t v) { write(offset, { static_cast(0x00ff & v), static_cast(v >> 8), }); } void BinaryContainer::writeInt32(size_t offset, const int32_t v) { write(offset, { static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::writeUint32(size_t offset, const uint32_t v) { write(offset, { static_cast(0x000000ff & v), static_cast(0x0000ff & (v >> 8)), static_cast(0x00ff & (v >> 16)), static_cast(v >> 24), }); } void BinaryContainer::writeChar(size_t offset, const char c) { buf_.at(offset) = c; } void BinaryContainer::writeString(size_t offset, const std::string str) { std::copy(str.begin(), str.end(), buf_.begin() + static_cast(offset)); } int8_t BinaryContainer::readInt8(size_t offset) const { return static_cast(buf_.at(offset)); } uint8_t BinaryContainer::readUint8(size_t offset) const { return static_cast(buf_.at(offset)); } int16_t BinaryContainer::readInt16(size_t offset) const { std::vector data = read(offset, 2); return static_cast(data[0] | (data[1] << 8)); } uint16_t BinaryContainer::readUint16(size_t offset) const { std::vector data = read(offset, 2); return static_cast(data[0] | (data[1] << 8)); } int32_t BinaryContainer::readInt32(size_t offset) const { std::vector data = read(offset, 4); return static_cast(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } uint32_t BinaryContainer::readUint32(size_t offset) const { std::vector data = read(offset, 4); return static_cast(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } char BinaryContainer::readChar(size_t offset) const { return buf_.at(offset); } std::string BinaryContainer::readString(size_t offset, size_t length) const { return std::string(buf_.begin() + static_cast(offset), buf_.begin() + static_cast(offset + length)); } BinaryContainer BinaryContainer::getSubcontainer(size_t offset, size_t length) const { std::vector tmp; std::copy_n(buf_.begin() + static_cast(offset), length, std::back_inserter(tmp)); return BinaryContainer(std::move(tmp)); } const char* BinaryContainer::getPointer() const { return &buf_[0]; } void BinaryContainer::append(std::vector a) { if (isLE_) std::copy(a.begin(), a.end(), std::back_inserter(buf_)); else std::reverse_copy(a.begin(), a.end(), std::back_inserter(buf_)); } void BinaryContainer::write(size_t offset, std::vector a) { if (isLE_) std::copy(a.begin(), a.end(), buf_.begin() + static_cast(offset)); else std::reverse_copy(a.begin(), a.end(), buf_.begin() + static_cast(offset)); } std::vector BinaryContainer::read(size_t offset, size_t size) const { std::vector data; if (isLE_) std::copy(buf_.begin() + static_cast(offset), buf_.begin() + static_cast(offset + size), std::back_inserter(data)); else std::reverse_copy(buf_.begin() + static_cast(offset), buf_.begin() + static_cast(offset + size), std::back_inserter(data)); return data; } BambooTracker-0.3.5/BambooTracker/io/binary_container.hpp000066400000000000000000000034741362177441300234330ustar00rootroot00000000000000#pragma once #include #include #include class BinaryContainer { public: explicit BinaryContainer(size_t defCapacity = 0); explicit BinaryContainer(std::vector buf); size_t size() const; void clear(); void reserve(size_t capacity); void setEndian(bool isLittleEndian); bool isLittleEndian() const; void appendInt8(const int8_t v); void appendUint8(const uint8_t v); void appendInt16(const int16_t v); void appendUint16(const uint16_t v); void appendInt32(const int32_t v); void appendUint32(const uint32_t v); void appendChar(const char c); void appendString(const std::string str); void appendArray(const uint8_t* array, size_t size); void appendVector(const std::vector vec); void appendVector(const std::vector vec); void writeInt8(size_t offset, const int8_t v); void writeUint8(size_t offset, const uint8_t v); void writeInt16(size_t offset, const int16_t v); void writeUint16(size_t offset, const uint16_t v); void writeInt32(size_t offset, const int32_t v); void writeUint32(size_t offset, const uint32_t v); void writeChar(size_t offset, const char c); void writeString(size_t offset, const std::string str); int8_t readInt8(size_t offset) const; uint8_t readUint8(size_t offset) const; int16_t readInt16(size_t offset) const; uint16_t readUint16(size_t offset) const; int32_t readInt32(size_t offset) const; uint32_t readUint32(size_t offset) const; char readChar(size_t offset) const; std::string readString(size_t offset, size_t length) const; BinaryContainer getSubcontainer(size_t offset, size_t length) const; const char* getPointer() const; private: std::vector buf_; bool isLE_; void append(std::vector a); void write(size_t offset, std::vector a); std::vector read(size_t offset, size_t size) const; }; BambooTracker-0.3.5/BambooTracker/io/export_handler.cpp000066400000000000000000000207631362177441300231160ustar00rootroot00000000000000#include "export_handler.hpp" #include "file_io.hpp" #include "file_io_error.hpp" ExportHandler::ExportHandler() {} void ExportHandler::writeWave(BinaryContainer& container, std::vector samples, uint32_t sampRate) { try { // RIFF header container.appendString("RIFF"); uint32_t offset = samples.size() * sizeof(short) + 36; container.appendUint32(offset); container.appendString("WAVE"); // fmt chunk container.appendString("fmt "); uint32_t chunkOfs = 16; container.appendUint32(chunkOfs); uint16_t fmtId = 1; container.appendUint16(fmtId); uint16_t chCnt = 2; container.appendUint16(chCnt); container.appendUint32(sampRate); uint16_t bitSize = sizeof(int16_t) * 8; uint16_t blockSize = bitSize / 8 * chCnt; uint32_t byteRate = blockSize * sampRate; container.appendUint32(byteRate); container.appendUint16(blockSize); container.appendUint16(bitSize); // Data chunk container.appendString("data"); uint32_t dataSize = samples.size() * bitSize / 8; container.appendUint32(dataSize); container.appendArray(reinterpret_cast(&samples[0]), dataSize); } catch (...) { throw FileOutputError(FileIO::FileType::WAV); } } void ExportHandler::writeVgm(BinaryContainer& container, int target, std::vector samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, uint32_t loopSamples, uint32_t totalSamples, bool gd3TagEnabled, GD3Tag tag) { uint32_t tagLen = 0; uint32_t tagDataLen = 0; if (gd3TagEnabled) { tagDataLen = tag.trackNameEn.length() + tag.trackNameJp.length() + tag.gameNameEn.length() + tag.gameNameJp.length() + tag.systemNameEn.length() + tag.systemNameJp.length() + tag.authorEn.length() + tag.authorJp.length() + tag.releaseDate.length() + tag.vgmCreator.length() + tag.notes.length(); tagLen = 12 + tagDataLen; } try { // Header // 0x00: "Vgm " ident uint8_t header[0x100] = {'V', 'g', 'm', ' '}; // 0x04: EOF offset uint32_t offset = 0x100 + samples.size() + 1 + tagLen - 4; *reinterpret_cast(header + 0x04) = offset; // 0x08: Version [v1.71] uint32_t version = 0x171; *reinterpret_cast(header + 0x08) = version; // 0x14: GD3 offset uint32_t gd3Offset = gd3TagEnabled ? (0x100 + samples.size() + 1 - 0x14) : 0; *reinterpret_cast(header + 0x14) = gd3Offset; // 0x18: Total # samples *reinterpret_cast(header + 0x18) = totalSamples; // 0x1c: Loop offset uint32_t loopOffset = loopFlag ? (loopPoint + 0x100 - 0x1c) : 0; *reinterpret_cast(header + 0x1c) = loopOffset; // 0x20: Loop # samples uint32_t loopSamps = loopFlag ? loopSamples : 0; *reinterpret_cast(header + 0x20) = loopSamps; // 0x24: Rate *reinterpret_cast(header + 0x24) = rate; // 0x34: VGM data offset uint32_t dataOffset = 0xcc; *reinterpret_cast(header + 0x34) = dataOffset; switch (target & Export_FmMask) { default: case Export_YM2608: // 0x48: YM2608 clock *reinterpret_cast(header + 0x48) = clock; break; case Export_YM2612: // 0x2c: YM2612 clock *reinterpret_cast(header + 0x2c) = clock; break; case Export_YM2203: // 0x44: YM2203 clock *reinterpret_cast(header + 0x44) = clock / 2; break; } switch (target & Export_SsgMask) { case Export_InternalSsg: break; default: // 0x74: AY8910 clock *reinterpret_cast(header + 0x74) = clock / 4; // 0x78: AY8910 chip type if ((target & Export_SsgMask) == Export_YM2149Psg) *reinterpret_cast(header + 0x78) = 0x10; // 0x79: AY8910 flags *reinterpret_cast(header + 0x79) = 0x01; break; } container.appendArray(header, 0x100); // Commands container.appendVector(samples); container.appendUint8(0x66); // End // GD3 tag if (gd3TagEnabled) { // "Gd3 " ident container.appendString("Gd3 "); // Version [v1.00] uint32_t gd3Version = 0x100; container.appendUint32(gd3Version); // Data size container.appendUint32(tagDataLen); // Track name in english container.appendString(tag.trackNameEn); // Track name in japanes container.appendString(tag.trackNameJp); // Game name in english container.appendString(tag.gameNameEn); // Game name in japanese container.appendString(tag.gameNameJp); // System name in english container.appendString(tag.systemNameEn); // System name in japanese container.appendString(tag.systemNameJp); // Track author in english container.appendString(tag.authorEn); // Track author in japanese container.appendString(tag.authorJp); // Release date container.appendString(tag.releaseDate); // VGM creator container.appendString(tag.vgmCreator); // Notes container.appendString(tag.notes); } } catch (...) { throw FileOutputError(FileIO::FileType::VGM); } } void ExportHandler::writeS98(BinaryContainer& container, int target, std::vector samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, bool tagEnabled, S98Tag tag) { try { // Header // 0x00: Magic "S98" container.appendString("S98"); // 0x03: Format version 3 uint8_t version = 0x33; container.appendUint8(version); // 0x04: Timer info (sync numerator) uint32_t timeNum = 1; container.appendUint32(timeNum); // 0x08: Timer info 2 (sync denominator) container.appendUint32(rate); // 0x0c: Deprecated uint32_t zero = 0; container.appendUint32(zero); // 0x10: Tag offset uint32_t tagOffset = tagEnabled ? (0x80 + samples.size() + 1) : 0; container.appendUint32(tagOffset); // 0x14: Dump data offset uint32_t dumpOffset = 0x80; container.appendUint32(dumpOffset); // 0x18: Loop offset uint32_t loopOffset = loopFlag ? (0x80 + loopPoint) : 0; container.appendUint32(loopOffset); // 0x1c: Device count uint32_t deviceCnt = 1; if ((target & Export_SsgMask) != Export_InternalSsg) deviceCnt = 2; container.appendUint32(deviceCnt); // 0x20-0x2f: Device info // 0x20: Device type uint32_t deviceType; uint32_t deviceClock; switch (target & Export_FmMask) { default: case Export_YM2608: deviceType = 4; // OPNA deviceClock = clock; break; case Export_YM2612: deviceType = 3; // OPN2 deviceClock = clock; break; case Export_YM2203: deviceType = 2; // OPN deviceClock = clock / 2; break; } container.appendUint32(deviceType); // 0x24: Clock container.appendUint32(deviceClock); // 0x28: Pan (Unused) container.appendUint32(zero); // 0x2c: Reserved container.appendUint32(zero); if ((target & Export_SsgMask) != Export_InternalSsg) { // 0x30-0x3f: Device info // 0x30: Device type uint32_t subdeviceType; uint32_t subdeviceClock; switch (target & Export_SsgMask) { default: case Export_AY8910Psg: subdeviceType = 15; // PSG (AY-3-8910) subdeviceClock = clock / 4; break; case Export_YM2149Psg: subdeviceType = 1; // PSG (YM 2149) subdeviceClock = clock / 2; break; } container.appendUint32(subdeviceType); // 0x34: Clock container.appendUint32(subdeviceClock); // 0x38: Pan (Unused) container.appendUint32(zero); // 0x3c: Reserved container.appendUint32(zero); } // 0x??-0x7f: Unused for (uint32_t i = 0; i < (24 - 4 * deviceCnt); ++i) container.appendUint32(zero); // Commands container.appendVector(samples); container.appendUint8(0xfd); // End // GD3 tag if (tagEnabled) { // Tag ident container.appendString("[S98]"); // BOM uint8_t bom[] = { 0xef, 0xbb, 0xbf }; container.appendArray(bom, 3); uint8_t nl = 0x0a; // Title container.appendString("title=" + tag.title); container.appendUint8(nl); // Artist container.appendString("artist=" + tag.artist); container.appendUint8(nl); // Game container.appendString("game=" + tag.game); container.appendUint8(nl); // Year container.appendString("year=" + tag.year); container.appendUint8(nl); // Genre container.appendString("genre=" + tag.genre); container.appendUint8(nl); // Comment container.appendString("comment=" + tag.comment); container.appendUint8(nl); // Copyright container.appendString("copyright=" + tag.copyright); container.appendUint8(nl); // S98by container.appendString("s98by=" + tag.s98by); container.appendUint8(nl); // System container.appendString("system=" + tag.system); container.appendUint8(nl); uint8_t end = 0; container.appendUint8(end); } } catch (...) { throw FileOutputError(FileIO::FileType::S98); } } BambooTracker-0.3.5/BambooTracker/io/export_handler.hpp000066400000000000000000000020551362177441300231150ustar00rootroot00000000000000#pragma once #include #include "gd3_tag.hpp" #include "s98_tag.hpp" #include "binary_container.hpp" class ExportHandler { public: static void writeWave(BinaryContainer& container, std::vector samples, uint32_t rate); static void writeVgm(BinaryContainer& container, int target, std::vector samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, uint32_t loopSamples, uint32_t totalSamples, bool gd3TagEnabled, GD3Tag tag); static void writeS98(BinaryContainer& container, int target, std::vector samples, uint32_t clock, uint32_t rate, bool loopFlag, uint32_t loopPoint, bool tagEnabled, S98Tag tag); private: ExportHandler(); }; enum ExportTargetFlag { /* target bits 0-2 : FM type */ Export_YM2608 = 0, Export_YM2612 = 1, Export_YM2203 = 2, Export_FmMask = Export_YM2608|Export_YM2612|Export_YM2203, /* target bit 3 : SSG type */ Export_InternalSsg = 0, Export_AY8910Psg = 4, Export_YM2149Psg = 8, Export_SsgMask = Export_InternalSsg|Export_AY8910Psg|Export_YM2149Psg, }; BambooTracker-0.3.5/BambooTracker/io/file_io.cpp000066400000000000000000000041361362177441300215020ustar00rootroot00000000000000#include "file_io.hpp" #include #include const FMEnvelopeParameter FileIO::ENV_FM_PARAMS[38] = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; const FMOperatorType FileIO::OP_FM_TYPES[4] = { FMOperatorType::Op1, FMOperatorType::Op2, FMOperatorType::Op3, FMOperatorType::Op4 }; std::string FileIO::getExtension(const std::string path) { std::string ext = path.substr(path.find_last_of(".") + 1); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); return ext; } FileIO::FileType FileIO::judgeFileTypeFromExtension(const std::string ext) { if (ext == "btm") return FileType::Mod; if (ext == "bti") return FileType::Inst; if (ext == "dmp") return FileType::Inst; if (ext == "tfi") return FileType::Inst; if (ext == "vgi") return FileType::Inst; if (ext == "opni") return FileType::Inst; if (ext == "y12") return FileType::Inst; if (ext == "ins") return FileType::Inst; if (ext == "btb") return FileType::Bank; if (ext == "wopn") return FileType::Bank; if (ext == "vgm") return FileType::VGM; if (ext == "s98") return FileType::S98; if (ext == "wav") return FileType::WAV; return FileType::Unknown; } BambooTracker-0.3.5/BambooTracker/io/file_io.hpp000066400000000000000000000006351362177441300215070ustar00rootroot00000000000000#pragma once #include #include "instruments_manager.hpp" class FileIO { public: static const FMEnvelopeParameter ENV_FM_PARAMS[38]; static const FMOperatorType OP_FM_TYPES[4]; enum class FileType { Mod, Inst, WAV, VGM, Bank, S98, Unknown }; static std::string getExtension(const std::string path); static FileType judgeFileTypeFromExtension(const std::string ext); private: FileIO() {} }; BambooTracker-0.3.5/BambooTracker/io/file_io_error.cpp000066400000000000000000000015031362177441300227060ustar00rootroot00000000000000#include "file_io_error.hpp" #include #include "misc.hpp" FileIOError::FileIOError(std::string text, const FileIO::FileType type) : std::runtime_error(text), type_(type) { } FileIO::FileType FileIOError::getFileType() const { return type_; } /******************************/ FileInputError::FileInputError(const FileIO::FileType type) : FileIOError("File input error", type) { } /******************************/ FileOutputError::FileOutputError(const FileIO::FileType type) : FileIOError("File output error", type) { } /******************************/ FileVersionError::FileVersionError(const FileIO::FileType type) : FileIOError("File version error", type) { } /******************************/ FileCorruptionError::FileCorruptionError(const FileIO::FileType type) : FileIOError("File corruption error", type) { } BambooTracker-0.3.5/BambooTracker/io/file_io_error.hpp000066400000000000000000000013001362177441300227060ustar00rootroot00000000000000#pragma once #include #include #include "file_io.hpp" class FileIOError : public std::runtime_error { public: FileIO::FileType getFileType() const; protected: FileIOError(std::string text, const FileIO::FileType type); const FileIO::FileType type_; }; class FileInputError : public FileIOError { public: FileInputError(const FileIO::FileType type); }; class FileOutputError : public FileIOError { public: FileOutputError(const FileIO::FileType type); }; class FileVersionError : public FileIOError { public: FileVersionError(const FileIO::FileType type); }; class FileCorruptionError : public FileIOError { public: FileCorruptionError(const FileIO::FileType type); }; BambooTracker-0.3.5/BambooTracker/io/gd3_tag.hpp000066400000000000000000000004211362177441300214020ustar00rootroot00000000000000#pragma once #include struct GD3Tag { std::string trackNameEn, trackNameJp; std::string gameNameEn, gameNameJp; std::string systemNameEn, systemNameJp; std::string authorEn, authorJp; std::string releaseDate; std::string vgmCreator; std::string notes; }; BambooTracker-0.3.5/BambooTracker/io/instrument_io.cpp000066400000000000000000004223771362177441300230060ustar00rootroot00000000000000#include "instrument_io.hpp" #include "file_io.hpp" #include #include #include #include #include "version.hpp" #include "file_io.hpp" #include "file_io_error.hpp" #include "pitch_converter.hpp" #include "enum_hash.hpp" #include "misc.hpp" InstrumentIO::InstrumentIO() {} void InstrumentIO::saveInstrument(BinaryContainer& ctr, std::weak_ptr instMan, int instNum) { ctr.appendString("BambooTrackerIst"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofInstrumentFileInBCD(); ctr.appendUint32(fileVersion); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset std::vector fmArpNums, fmPtNums; std::shared_ptr inst = instMan.lock()->getInstrumentSharedPtr(instNum); if (inst) { std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getSoundSource()) { case SoundSource::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); if (instFM->getArpeggioEnabled(FMOperatorType::All)) fmArpNums.push_back(instFM->getArpeggioNumber(FMOperatorType::All)); for (auto& t : FileIO::OP_FM_TYPES) { if (instFM->getArpeggioEnabled(t)) { int n = instFM->getArpeggioNumber(t); if (std::find(fmArpNums.begin(), fmArpNums.end(), n) == fmArpNums.end()) fmArpNums.push_back(n); } } if (instFM->getArpeggioEnabled(FMOperatorType::All)) { int n = instFM->getArpeggioNumber(FMOperatorType::All); for (size_t i = 0; i < fmArpNums.size(); ++i) { if (n == fmArpNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } for (auto& t : FileIO::OP_FM_TYPES) { if (instFM->getArpeggioEnabled(t)) { int n = instFM->getArpeggioNumber(t); for (size_t i = 0; i < fmArpNums.size(); ++i) { if (n == fmArpNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } } if (instFM->getPitchEnabled(FMOperatorType::All)) fmPtNums.push_back(instFM->getPitchNumber(FMOperatorType::All)); for (auto& t : FileIO::OP_FM_TYPES) { if (instFM->getPitchEnabled(t)) { int n = instFM->getPitchNumber(t); if (std::find(fmPtNums.begin(), fmPtNums.end(), n) == fmPtNums.end()) fmPtNums.push_back(n); } } if (instFM->getPitchEnabled(FMOperatorType::All)) { int n = instFM->getPitchNumber(FMOperatorType::All); for (size_t i = 0; i < fmPtNums.size(); ++i) { if (n == fmPtNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } for (auto& t : FileIO::OP_FM_TYPES) { if (instFM->getPitchEnabled(t)) { int n = instFM->getPitchNumber(t); for (size_t i = 0; i < fmPtNums.size(); ++i) { if (n == fmPtNums[i]) { ctr.appendUint8(static_cast(i)); break; } } } else { ctr.appendUint8(0x80); } } break; } case SoundSource::SSG: { ctr.appendUint8(0x01); break; } case SoundSource::DRUM: break; } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset switch (inst->getSoundSource()) { case SoundSource::FM: { auto instFM = std::dynamic_pointer_cast(inst); // FM envelope int envNum = instFM->getEnvelopeNumber(); { ctr.appendUint8(0x00); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AL) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); // Operator 1 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(envNum, 0); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL1) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL1)); ctr.appendUint8(tmp); int tmp2 = instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG1); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML1)); ctr.appendUint8(tmp); // Operator 2 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(envNum, 1); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL2) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL2)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG2); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML2)); ctr.appendUint8(tmp); // Operator 3 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(envNum, 2); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL3) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL3)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG3); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML3)); ctr.appendUint8(tmp); // Operator 4 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(envNum, 3); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL4) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL4)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG4); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML4)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } // FM LFO if (instFM->getLFOEnabled()) { int lfoNum = instFM->getLFONumber(); ctr.appendUint8(0x01); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::FREQ) << 4) | static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::AM4) << 7) | static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::AM3) << 6) | static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::AM2) << 5) | static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::AM1) << 4) | static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(lfoNum, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { if (instFM->getOperatorSequenceEnabled(FileIO::ENV_FM_PARAMS[i])) { int seqNum = instFM->getOperatorSequenceNumber(FileIO::ENV_FM_PARAMS[i]); ctr.appendUint8(0x02 + static_cast(i)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getOperatorSequenceFMSequence(FileIO::ENV_FM_PARAMS[i], seqNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getOperatorSequenceFMLoops(FileIO::ENV_FM_PARAMS[i], seqNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getOperatorSequenceFMRelease(FileIO::ENV_FM_PARAMS[i], seqNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM arpeggio for (int& arpNum : fmArpNums) { ctr.appendUint8(0x28); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioFMSequence(arpNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioFMLoops(arpNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioFMRelease(arpNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioFMType(arpNum)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // FM pitch for (int& ptNum : fmPtNums) { ctr.appendUint8(0x29); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchFMSequence(ptNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchFMLoops(ptNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchFMRelease(ptNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchFMType(ptNum)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } break; } case SoundSource::SSG: { auto instSSG = std::dynamic_pointer_cast(inst); // SSG wave form if (instSSG->getWaveFormEnabled()) { int wfNum = instSSG->getWaveFormNumber(); ctr.appendUint8(0x30); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getWaveFormSSGSequence(wfNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getWaveFormSSGLoops(wfNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getWaveFormSSGRelease(wfNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG tone/noise if (instSSG->getToneNoiseEnabled()) { int tnNum = instSSG->getToneNoiseNumber(); ctr.appendUint8(0x31); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getToneNoiseSSGSequence(tnNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getToneNoiseSSGLoops(tnNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getToneNoiseSSGRelease(tnNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG envelope if (instSSG->getEnvelopeEnabled()) { int envNum = instSSG->getEnvelopeNumber(); ctr.appendUint8(0x32); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getEnvelopeSSGSequence(envNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getEnvelopeSSGLoops(envNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getEnvelopeSSGRelease(envNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG arpeggio if (instSSG->getArpeggioEnabled()) { int arpNum = instSSG->getArpeggioNumber(); ctr.appendUint8(0x33); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioSSGSequence(arpNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioSSGLoops(arpNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioSSGRelease(arpNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioSSGType(arpNum)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } // SSG pitch if (instSSG->getPitchEnabled()) { int ptNum = instSSG->getPitchNumber(); ctr.appendUint8(0x34); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchSSGSequence(ptNum); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchSSGLoops(ptNum); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchSSGRelease(ptNum); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchSSGType(ptNum)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } break; } case SoundSource::DRUM: break; } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } AbstractInstrument* InstrumentIO::loadInstrument(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum) { std::string ext = FileIO::getExtension(path); if (ext.compare("dmp") == 0) return InstrumentIO::loadDMPFile(ctr, path, instMan, instNum); if (ext.compare("tfi") == 0) return InstrumentIO::loadTFIFile(ctr, path, instMan, instNum); if (ext.compare("vgi") == 0) return InstrumentIO::loadVGIFile(ctr, path, instMan, instNum); if (ext.compare("opni") == 0) return InstrumentIO::loadOPNIFile(ctr, instMan, instNum); if (ext.compare("y12") == 0) return InstrumentIO::loadY12File(ctr, path, instMan, instNum); if (ext.compare("ins") == 0) return InstrumentIO::loadINSFile(ctr, instMan, instNum); if (ext.compare("bti") == 0) return InstrumentIO::loadBTIFile(ctr, instMan, instNum); throw FileInputError(FileIO::FileType::Inst); } AbstractInstrument* InstrumentIO::loadBTIFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum) { size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerIst") throw FileCorruptionError(FileIO::FileType::Inst); globCsr += 16; /*size_t eofOfs = */ctr.readUint32(globCsr); globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofInstrumentFileInBCD()) throw FileVersionError(FileIO::FileType::Inst); globCsr += 4; /***** Instrument section *****/ if (ctr.readString(globCsr, 8) != "INSTRMNT") throw FileCorruptionError(FileIO::FileType::Inst); else { globCsr += 8; size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; size_t nameLen = ctr.readUint32(instCsr); instCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(instCsr, nameLen); instCsr += nameLen; } std::unordered_map fmArpMap, fmPtMap; AbstractInstrument* inst = nullptr; switch (ctr.readUint8(instCsr++)) { case 0x00: // FM { inst = new InstrumentFM(instNum, name, instMan.lock().get()); auto fm = dynamic_cast(inst); uint8_t tmp = ctr.readUint8(instCsr++); fm->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); fm->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); fm->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); fm->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); fm->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); if (fileVersion >= Version::toBCD(1, 1, 0)) { fmArpMap.emplace(FMOperatorType::All, ctr.readUint8(instCsr++)); for (auto& t : FileIO::OP_FM_TYPES) { tmp = ctr.readUint8(instCsr++); if (!(tmp & 0x80)) fmArpMap.emplace(t, (tmp & 0x7f)); } fmPtMap.emplace(FMOperatorType::All, ctr.readUint8(instCsr++)); for (auto& t : FileIO::OP_FM_TYPES) { tmp = ctr.readUint8(instCsr++); if (!(tmp & 0x80)) fmPtMap.emplace(t, (tmp & 0x7f)); } } break; } case 0x01: // SSG { inst = new InstrumentSSG(instNum, name, instMan.lock().get()); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } globCsr += instOfs; /***** Instrument property section *****/ if (ctr.readString(globCsr, 8) != "INSTPROP") throw FileCorruptionError(FileIO::FileType::Inst); else { globCsr += 8; size_t instPropOfs = ctr.readUint32(globCsr); size_t instPropCsr = globCsr + 4; size_t instPropCsrTmp = instPropCsr; globCsr += instPropOfs; std::vector nums; // Check memory range while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { nums.push_back(instMan.lock()->findFirstFreePlainEnvelopeFM()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint8(instPropCsr); break; } case 0x01: // FM LFO { nums.push_back(instMan.lock()->findFirstFreePlainLFOFM()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint8(instPropCsr); break; } case 0x02: // FM AL { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::AL)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x03: // FM FB { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::FB)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x04: // FM AR1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::AR1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x05: // FM DR1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DR1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x06: // FM SR1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SR1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x07: // FM RR1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::RR1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x08: // FM SL1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SL1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x09: // FM TL1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::TL1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0a: // FM KS1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::KS1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0b: // FM ML1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::ML1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0c: // FM DT1 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DT1)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0d: // FM AR2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::AR2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0e: // FM DR2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DR2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x0f: // FM SR2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SR2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x10: // FM RR2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::RR2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x11: // FM SL2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SL2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x12: // FM TL2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::TL2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x13: // FM KS2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::KS2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x14: // FM ML2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::ML2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x15: // FM DT2 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DT2)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x16: // FM AR3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::AR3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x17: // FM DR3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DR3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x18: // FM SR3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SR3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x19: // FM RR3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::RR3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1a: // FM SL3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SL3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1b: // FM TL3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::TL3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1c: // FM KS3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::KS3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1d: // FM ML3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::ML3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1e: // FM DT3 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DT3)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x1f: // FM AR4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::AR4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x20: // FM DR4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DR4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x21: // FM SR4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SR4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x22: // FM RR4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::RR4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x23: // FM SL4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::SL4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x24: // FM TL4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::TL4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x25: // FM KS4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::KS4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x26: // FM ML4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::ML4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x27: // FM DT4 { nums.push_back(instMan.lock()->findFirstFreePlainOperatorSequenceFM(FMEnvelopeParameter::DT4)); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x28: // FM arpeggio { nums.push_back(instMan.lock()->findFirstFreePlainArpeggioFM()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x29: // FM pitch { nums.push_back(instMan.lock()->findFirstFreePlainPitchFM()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x30: // SSG wave form { nums.push_back(instMan.lock()->findFirstFreePlainWaveFormSSG()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x31: // SSG tone/noise { nums.push_back(instMan.lock()->findFirstFreePlainToneNoiseSSG()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x32: // SSG envelope { nums.push_back(instMan.lock()->findFirstFreePlainEnvelopeSSG()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x33: // SSG arpeggio { nums.push_back(instMan.lock()->findFirstFreePlainArpeggioSSG()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } case 0x34: // SSG pitch { nums.push_back(instMan.lock()->findFirstFreePlainPitchSSG()); if (nums.back() == -1) throw FileCorruptionError(FileIO::FileType::Inst); instPropCsr += ctr.readUint16(instPropCsr); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } } // Read data instPropCsr = instPropCsrTmp; auto numIt = nums.begin(); while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { int idx = *numIt++; dynamic_cast(inst)->setEnvelopeNumber(idx); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AL, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::FB, tmp & 0x0f); // Operator 1 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 0, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL1, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR1, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL1, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML1, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG1, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 2 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 1, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL2, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR2, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL2, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML2, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG2, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 3 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 2, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL3, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR3, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL3, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML3, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG3, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 4 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 3, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL4, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR4, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL4, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML4, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG4, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); instPropCsr += ofs; break; } case 0x01: // FM LFO { int idx = *numIt++; auto fm = dynamic_cast(inst); fm->setLFOEnabled(true); fm->setLFONumber(idx); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::FREQ, tmp >> 4); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::PMS, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AMS, tmp & 0x0f); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::Count, tmp); instPropCsr += ofs; break; } case 0x02: // FM AL { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AL, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x03: // FM FB { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::FB, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x04: // FM AR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x05: // FM DR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x06: // FM SR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x07: // FM RR1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x08: // FM SL1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x09: // FM TL1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0a: // FM KS1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0b: // FM ML1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0c: // FM DT1 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT1, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0d: // FM AR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0e: // FM DR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x0f: // FM SR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x10: // FM RR2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x11: // FM SL2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x12: // FM TL2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x13: // FM KS2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x14: // FM ML2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x15: // FM DT2 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT2, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x16: // FM AR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x17: // FM DR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x18: // FM SR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x19: // FM RR3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1a: // FM SL3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1b: // FM TL3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1c: // FM KS3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1d: // FM ML3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1e: // FM DT3 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT3, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x1f: // FM AR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::AR4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x20: // FM DR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DR4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x21: // FM SR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SR4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x22: // FM RR4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::RR4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x23: // FM SL4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::SL4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x24: // FM TL4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::TL4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x25: // FM KS4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::KS4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x26: // FM ML4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::ML4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x27: // FM DT4 { instPropCsr += loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter::DT4, instPropCsr, instMan, ctr, dynamic_cast(inst), *numIt++, fileVersion); break; } case 0x28: // FM arpeggio { int idx = *numIt++; auto fm = dynamic_cast(inst); if (fileVersion >= Version::toBCD(1, 1, 0)) { std::vector del; for (auto& pair : fmArpMap) { if (pair.second-- == 0) { fm->setArpeggioEnabled(pair.first, true); fm->setArpeggioNumber(pair.first, idx); del.push_back(pair.first); } } for (auto t : del) fmArpMap.erase(t); } else { fm->setArpeggioEnabled(FMOperatorType::All, true); fm->setArpeggioNumber(FMOperatorType::All, idx); } uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setArpeggioFMSequenceCommand(idx, 0, data, 0); else instMan.lock()->addArpeggioFMSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setArpeggioFMLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioFMType(idx, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioFMType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Inst); } } } instPropCsr += ofs; break; } case 0x29: // FM pitch { int idx = *numIt++; auto fm = dynamic_cast(inst); if (fileVersion >= Version::toBCD(1, 1, 0)) { std::vector del; for (auto& pair : fmPtMap) { if (pair.second-- == 0) { fm->setPitchEnabled(pair.first, true); fm->setPitchNumber(pair.first, idx); del.push_back(pair.first); } } for (auto t : del) fmPtMap.erase(t); } else { fm->setPitchEnabled(FMOperatorType::All, true); fm->setPitchNumber(FMOperatorType::All, idx); } uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setPitchFMSequenceCommand(idx, 0, data, 0); else instMan.lock()->addPitchFMSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setPitchFMLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setPitchFMRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchFMRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchFMRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setPitchFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchFMType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Inst); } } } instPropCsr += ofs; break; } case 0x30: // SSG wave form { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setWaveFormEnabled(true); ssg->setWaveFormNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) { if (data == 3) data = static_cast(SSGWaveFormType::SQM_TRIANGLE); else if (data == 4) data = static_cast(SSGWaveFormType::SQM_SAW); } int32_t subdata; if (fileVersion >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; if (subdata != -1) subdata = PitchConverter::getPitchSSGSquare(subdata); } if (l == 0) instMan.lock()->setWaveFormSSGSequenceCommand(idx, 0, data, subdata); else instMan.lock()->addWaveFormSSGSequenceCommand(idx, data, subdata); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setWaveFormSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x31: // SSG tone/noise { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setToneNoiseEnabled(true); ssg->setToneNoiseNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 2)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setToneNoiseSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addToneNoiseSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setToneNoiseSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x32: // SSG envelope { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setEnvelopeEnabled(true); ssg->setEnvelopeNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; int32_t subdata; if (fileVersion >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; } if (l == 0) instMan.lock()->setEnvelopeSSGSequenceCommand(idx, 0, data, subdata); else instMan.lock()->addEnvelopeSSGSequenceCommand(idx, data, subdata); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setEnvelopeSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::AbsoluteRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::RelativeRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; break; } case 0x33: // SSG arpeggio { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setArpeggioEnabled(true); ssg->setArpeggioNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setArpeggioSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addArpeggioSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setArpeggioSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioSSGType(idx, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioSSGType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Inst); } } } instPropCsr += ofs; break; } case 0x34: // SSG pitch { int idx = *numIt++; auto ssg = dynamic_cast(inst); ssg->setPitchEnabled(true); ssg->setPitchNumber(idx); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (fileVersion < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setPitchSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addPitchSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setPitchSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setPitchSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (fileVersion >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setPitchSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchSSGType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (fileVersion < Version::toBCD(1, 2, 3)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Inst); } } } instPropCsr += ofs; break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } } } return inst; } } size_t InstrumentIO::loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter param, size_t instMemCsr, std::weak_ptr instMan, BinaryContainer& ctr, InstrumentFM* inst, int idx, uint32_t version) { inst->setOperatorSequenceEnabled(param, true); inst->setOperatorSequenceNumber(param, idx); uint16_t ofs = ctr.readUint16(instMemCsr); size_t csr = instMemCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 1)) csr += 2; if (l == 0) instMan.lock()->setOperatorSequenceFMSequenceCommand(param, idx, 0, data, 0); else instMan.lock()->addOperatorSequenceFMSequenceCommand(param, idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setOperatorSequenceFMLoops(param, idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Inst); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } return ofs; } AbstractInstrument* InstrumentIO::loadDMPFile(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum) { size_t fnpos = path.find_last_of("/"); std::string name = path.substr(fnpos + 1, path.find_last_of(".") - fnpos - 1); size_t csr = 0; uint8_t insType = 1; // default to FM uint8_t fileVersion = ctr.readUint8(csr++); if (fileVersion == 0) { // older, unversioned dmp if (ctr.size() != 49) throw FileCorruptionError(FileIO::FileType::Inst); } else { if (fileVersion < 9) throw FileCorruptionError(FileIO::FileType::Inst); if (fileVersion == 9 && ctr.size() != 51) { // make sure it's not for that discontinued chip throw FileCorruptionError(FileIO::FileType::Inst); } uint8_t system = 2; // default to genesis if (fileVersion >= 11) system = ctr.readUint8(csr++); if (system != 2 && system != 3 && system != 8) { // genesis, sms and arcade only throw FileCorruptionError(FileIO::FileType::Inst); } insType = ctr.readUint8(csr++); } AbstractInstrument* inst = nullptr; switch (insType) { case 0x00: // SSG { inst = new InstrumentSSG(instNum, name, instMan.lock().get()); auto ssg = dynamic_cast(inst); uint8_t envSize = ctr.readUint8(csr++); if (envSize > 0) { int idx = instMan.lock()->findFirstFreePlainEnvelopeSSG(); if (idx < 0) throw FileCorruptionError(FileIO::FileType::Inst); ssg->setEnvelopeEnabled(true); ssg->setEnvelopeNumber(idx); for (uint8_t l = 0; l < envSize; ++l) { int data = ctr.readInt32(csr); // compensate SN76489's envelope step of 2dB to SSG's 3dB if (data > 0) data = 15 - (15 - data) * 2 / 3; csr += 4; if (l == 0) instMan.lock()->setEnvelopeSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addEnvelopeSSGSequenceCommand(idx, data, 0); } int8_t loop = ctr.readInt8(csr++); if (loop >= 0) instMan.lock()->setEnvelopeSSGLoops(idx, {loop}, {envSize - 1}, {1}); } uint8_t arpSize = ctr.readUint8(csr++); if (arpSize > 0) { int idx = instMan.lock()->findFirstFreePlainArpeggioSSG(); if (idx < 0) throw FileCorruptionError(FileIO::FileType::Inst); ssg->setArpeggioEnabled(true); ssg->setArpeggioNumber(idx); uint8_t arpType = ctr.readUint8(csr + arpSize * 4 + 1); if (arpType == 1) instMan.lock()->setArpeggioSSGType(idx, SequenceType::FIXED_SEQUENCE); for (uint8_t l = 0; l < arpSize; ++l) { int data = ctr.readInt32(csr) + 36; csr += 4; if (arpType == 1) data -= 24; if (l == 0) instMan.lock()->setArpeggioSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addArpeggioSSGSequenceCommand(idx, data, 0); } int8_t loop = ctr.readInt8(csr++); if (loop >= 0) instMan.lock()->setArpeggioSSGLoops(idx, {loop}, {arpSize - 1}, {1}); } break; } case 0x01: // FM { int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); inst = new InstrumentFM(instNum, name, instMan.lock().get()); auto fm = dynamic_cast(inst); fm->setEnvelopeNumber(envIdx); if (fileVersion == 9) csr++; // skip version 9's total operators field uint8_t pms = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(csr++)); uint8_t ams = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR1, ctr.readUint8(csr++)); uint8_t am1 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT1, convertTFIVGMDT(ctr.readUint8(csr++) & 15)); // mask out OPM's DT2 instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR1, ctr.readUint8(csr++)); int ssgeg1 = ctr.readUint8(csr++); ssgeg1 = ssgeg1 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG1, ssgeg1); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR3, ctr.readUint8(csr++)); uint8_t am3 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT3, convertTFIVGMDT(ctr.readUint8(csr++) & 15)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR3, ctr.readUint8(csr++)); int ssgeg3 = ctr.readUint8(csr++); ssgeg3 = ssgeg3 & 8 ? ssgeg3 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG3, ssgeg3); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR2, ctr.readUint8(csr++)); uint8_t am2 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT2, convertTFIVGMDT(ctr.readUint8(csr++) & 15)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR2, ctr.readUint8(csr++)); int ssgeg2 = ctr.readUint8(csr++); ssgeg2 = ssgeg2 & 8 ? ssgeg2 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG2, ssgeg2); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR4, ctr.readUint8(csr++)); uint8_t am4 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT4, convertTFIVGMDT(ctr.readUint8(csr++) & 15)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR4, ctr.readUint8(csr++)); int ssgeg4 = ctr.readUint8(csr++); ssgeg4 = ssgeg4 & 8 ? ssgeg4 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG4, ssgeg4); if (pms || ams) { int lfoIdx = instMan.lock()->findFirstFreePlainLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); fm->setLFOEnabled(true); fm->setLFONumber(lfoIdx); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, pms); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, ams); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, am1); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, am2); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, am3); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, am4); } break; } } return inst; } size_t InstrumentIO::getPropertyPositionForBTB(const BinaryContainer& propCtr, uint8_t subsecType, uint8_t index) { size_t csr = 0; while (csr < propCtr.size()) { uint8_t type = propCtr.readUint8(csr++); bool isSection = (type == subsecType); size_t bcnt = propCtr.readUint8(csr++); for (size_t i = 0; i < bcnt; ++i) { if (isSection) { if (propCtr.readUint8(csr++) == index) { switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += 1; break; default: // Sequence csr += 2; break; } return csr; } else { switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += propCtr.readUint8(csr); break; default: // Sequence csr += propCtr.readUint16(csr); break; } } } else { ++csr; // Skip index switch (type) { case 0x00: // FM envelope case 0x01: // FM LFO csr += propCtr.readUint8(csr); break; default: // Sequence csr += propCtr.readUint16(csr); break; } } } } throw FileCorruptionError(FileIO::FileType::Inst); } int InstrumentIO::convertTFIVGMDT(int dt) { switch (dt) { case 0: return 7; case 1: return 6; case 2: return 5; case 3: return 0; case 4: return 1; case 5: return 2; case 6: return 3; default: throw std::out_of_range("Out of range dt"); } } AbstractInstrument* InstrumentIO::loadTFIFile(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum) { if (ctr.size() != 42) throw FileCorruptionError(FileIO::FileType::Inst); int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); size_t fnpos = path.find_last_of("/"); std::string name = path.substr(fnpos + 1, path.find_last_of(".") - fnpos - 1); size_t csr = 0; InstrumentFM* inst = new InstrumentFM(instNum, name, instMan.lock().get()); inst->setEnvelopeNumber(envIdx); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT1, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL1, ctr.readUint8(csr++)); int ssgeg1 = ctr.readUint8(csr++); ssgeg1 = ssgeg1 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG1, ssgeg1); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT3, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL3, ctr.readUint8(csr++)); int ssgeg3 = ctr.readUint8(csr++); ssgeg3 = ssgeg3 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG3, ssgeg3); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT2, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL2, ctr.readUint8(csr++)); int ssgeg2 = ctr.readUint8(csr++); ssgeg2 = ssgeg2 & 8 ? ssgeg2 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG2, ssgeg2); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT4, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL4, ctr.readUint8(csr++)); int ssgeg4 = ctr.readUint8(csr++); ssgeg4 = ssgeg4 & 8 ? ssgeg4 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG4, ssgeg4); return inst; } AbstractInstrument* InstrumentIO::loadVGIFile(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum) { if (ctr.size() != 43) throw FileCorruptionError(FileIO::FileType::Inst); int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); size_t fnpos = path.find_last_of("/"); std::string name = path.substr(fnpos + 1, path.find_last_of(".") - fnpos - 1); size_t csr = 0; InstrumentFM* inst = new InstrumentFM(instNum, name, instMan.lock().get()); inst->setEnvelopeNumber(envIdx); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(csr++)); uint8_t pams = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT1, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR1, ctr.readUint8(csr++)); uint8_t drams1 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR1, drams1 & 31); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL1, ctr.readUint8(csr++)); int ssgeg1 = ctr.readUint8(csr++); ssgeg1 = ssgeg1 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG1, ssgeg1); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT3, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR3, ctr.readUint8(csr++)); uint8_t drams3 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR3, drams3 & 31); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL3, ctr.readUint8(csr++)); int ssgeg3 = ctr.readUint8(csr++); ssgeg3 = ssgeg3 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG3, ssgeg3); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT2, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR2, ctr.readUint8(csr++)); uint8_t drams2 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR2, drams2 & 31); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL2, ctr.readUint8(csr++)); int ssgeg2 = ctr.readUint8(csr++); ssgeg2 = ssgeg2 & 8 ? ssgeg2 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG2, ssgeg2); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT4, convertTFIVGMDT(ctr.readUint8(csr++))); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR4, ctr.readUint8(csr++)); uint8_t drams4 = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR4, drams4 & 31); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL4, ctr.readUint8(csr++)); int ssgeg4 = ctr.readUint8(csr++); ssgeg4 = ssgeg4 & 8 ? ssgeg4 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG4, ssgeg4); if (pams != 0) { int lfoIdx = instMan.lock()->findFirstFreePlainLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); inst->setLFOEnabled(true); inst->setLFONumber(lfoIdx); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, pams & 7); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, pams >> 4); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, drams1 >> 7); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, drams2 >> 7); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, drams3 >> 7); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, drams4 >> 7); } return inst; } AbstractInstrument* InstrumentIO::loadOPNIFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum) { OPNIFile opni; if (WOPN_LoadInstFromMem(&opni, const_cast(ctr.getPointer()), static_cast(ctr.size())) != 0) throw FileCorruptionError(FileIO::FileType::Inst); return loadWOPNInstrument(opni.inst, instMan, instNum); } AbstractInstrument* InstrumentIO::loadY12File(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum) { if (ctr.size() != 128) throw FileCorruptionError(FileIO::FileType::Inst); int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); size_t fnpos = path.find_last_of("/"); std::string name = path.substr(fnpos + 1, path.find_last_of(".") - fnpos - 1); size_t csr = 0; InstrumentFM* inst = new InstrumentFM(instNum, name, instMan.lock().get()); inst->setEnvelopeNumber(envIdx); uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML1, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT1, 0x07 & (tmp >> 4)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL1, 0x7f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR1, 0x1f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS1, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR1, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR1, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR1, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL1, tmp >> 4); int ssgeg1 = ctr.readUint8(csr++); ssgeg1 = ssgeg1 & 8 ? ssgeg1 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG1, ssgeg1); csr += 9; tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML3, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT3, 0x07 & (tmp >> 4)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL3, 0x7f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR3, 0x1f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS3, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR3, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR3, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR3, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL3, tmp >> 4); int ssgeg3 = ctr.readUint8(csr++); ssgeg3 = ssgeg3 & 8 ? ssgeg3 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG3, ssgeg3); csr += 9; tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML2, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT2, 0x07 & (tmp >> 4)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL2, 0x7f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR2, 0x1f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS2, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR2, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR2, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR2, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL2, tmp >> 4); int ssgeg2 = ctr.readUint8(csr++); ssgeg2 = ssgeg2 & 8 ? ssgeg2 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG2, ssgeg2); csr += 9; tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML4, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT4, 0x07 & (tmp >> 4)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL4, 0x7f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR4, 0x1f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS4, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR4, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR4, 0x1f & tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR4, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL4, tmp >> 4); int ssgeg4 = ctr.readUint8(csr++); ssgeg4 = ssgeg4 & 8 ? ssgeg4 & 7 : -1; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG4, ssgeg4); csr += 9; instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, ctr.readUint8(csr++)); csr += 14; return inst; } AbstractInstrument* InstrumentIO::loadINSFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum) { size_t csr = 0; if (ctr.readString(csr, 4).compare("MVSI") != 0) throw FileInputError(FileIO::FileType::Inst); csr += 4; /*uint8_t fileVersion = */std::stoi(ctr.readString(csr++, 1)); size_t nameCsr = 0; while (ctr.readChar(nameCsr++) != '\0') ; std::string name = ctr.readString(csr, nameCsr - csr); csr = nameCsr; if (ctr.size() - csr != 25) throw FileInputError(FileIO::FileType::Inst); int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Inst); InstrumentFM* inst = new InstrumentFM(instNum, name, instMan.lock().get()); inst->setEnvelopeNumber(envIdx); uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML1, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT1, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML2, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT2, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML3, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT3, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML4, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT4, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL4, ctr.readUint8(csr++)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR1, 0x3f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS1, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR2, 0x3f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS2, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR3, 0x3f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS3, tmp >> 6); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR4, 0x3f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS4, tmp >> 6); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR4, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR1, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR2, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR3, ctr.readUint8(csr++)); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR4, ctr.readUint8(csr++)); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR1, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL1, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR2, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL2, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR3, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL3, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR4, 0x0f & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL4, tmp >> 4); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, 0x07 & tmp); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, tmp >> 3); return inst; } AbstractInstrument* InstrumentIO::loadWOPNInstrument(const WOPNInstrument &srcInst, std::weak_ptr instMan, int instNum) { int envIdx = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envIdx < 0) throw FileCorruptionError(FileIO::FileType::Bank); const char *name = srcInst.inst_name; InstrumentFM* inst = new InstrumentFM(instNum, name, instMan.lock().get()); inst->setEnvelopeNumber(envIdx); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AL, srcInst.fbalg & 7); instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::FB, (srcInst.fbalg >> 3) & 7); const WOPNOperator *op[4] = { &srcInst.operators[0], &srcInst.operators[2], &srcInst.operators[1], &srcInst.operators[3], }; #define LOAD_OPERATOR(n) \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::ML##n, op[n - 1]->dtfm_30 & 15); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DT##n, (op[n - 1]->dtfm_30 >> 4) & 7); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::TL##n, op[n - 1]->level_40); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::KS##n, op[n - 1]->rsatk_50 >> 6); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::AR##n, op[n - 1]->rsatk_50 & 31); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::DR##n, op[n - 1]->amdecay1_60 & 31); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SR##n, op[n - 1]->decay2_70 & 31); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::RR##n, op[n - 1]->susrel_80 & 15); \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SL##n, op[n - 1]->susrel_80 >> 4); \ int ssgeg##n = op[n - 1]->ssgeg_90; \ ssgeg##n = ssgeg##n & 8 ? ssgeg##n & 7 : -1; \ instMan.lock()->setEnvelopeFMParameter(envIdx, FMEnvelopeParameter::SSGEG##n, ssgeg##n); \ int am##n = op[n - 1]->amdecay1_60 >> 7; LOAD_OPERATOR(1) LOAD_OPERATOR(2) LOAD_OPERATOR(3) LOAD_OPERATOR(4) #undef LOAD_OPERATOR if (srcInst.lfosens != 0) { int lfoIdx = instMan.lock()->findFirstFreePlainLFOFM(); if (lfoIdx < 0) throw FileCorruptionError(FileIO::FileType::Bank); inst->setLFOEnabled(true); inst->setLFONumber(lfoIdx); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::PMS, srcInst.lfosens & 7); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AMS, (srcInst.lfosens >> 4) & 3); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM1, am1); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM2, am2); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM3, am3); instMan.lock()->setLFOFMParameter(lfoIdx, FMLFOParameter::AM4, am4); } if (srcInst.note_offset != 0) { int arpIdx = instMan.lock()->findFirstFreePlainArpeggioFM(); if (arpIdx < 0) throw FileCorruptionError(FileIO::FileType::Bank); inst->setArpeggioEnabled(FMOperatorType::All, true); inst->setArpeggioNumber(FMOperatorType::All, arpIdx); instMan.lock()->setArpeggioFMSequenceCommand(arpIdx, 0, srcInst.note_offset + 48, -1); instMan.lock()->setArpeggioFMType(arpIdx, SequenceType::ABSOLUTE_SEQUENCE); } return inst; } AbstractInstrument* InstrumentIO::loadBTBInstrument(BinaryContainer instCtr, BinaryContainer propCtr, std::weak_ptr instMan, int instNum, uint32_t bankVersion) { size_t instCsr = 5; // Skip instrument id and offset size_t nameLen = instCtr.readUint32(instCsr); instCsr += 4; std::string name = u8""; if (nameLen > 0) { name = instCtr.readString(instCsr, nameLen); instCsr += nameLen; } switch (instCtr.readUint8(instCsr++)) { case 0x00: // FM { auto fm = new InstrumentFM(instNum, name, instMan.lock().get()); /* Envelope */ { auto orgEnvNum = instCtr.readUint8(instCsr++); int envNum = instMan.lock()->findFirstFreePlainEnvelopeFM(); if (envNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); fm->setEnvelopeNumber(envNum); size_t envCsr = getPropertyPositionForBTB(propCtr, 0x00, orgEnvNum); uint8_t tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AL, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::FB, tmp & 0x0f); // Operator 1 tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMOperatorEnabled(envNum, 0, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR1, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR1, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR1, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL1, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR1, tmp & 0x0f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL1, tmp); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML1, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG1, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 2 tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMOperatorEnabled(envNum, 1, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR2, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR2, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR2, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL2, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR2, tmp & 0x0f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL2, tmp); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML2, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG2, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 3 tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMOperatorEnabled(envNum, 2, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR3, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR3, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR3, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL3, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR3, tmp & 0x0f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL3, tmp); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML3, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG3, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 4 tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMOperatorEnabled(envNum, 3, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::AR4, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::KS4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DR4, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::DT4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SR4, tmp & 0x1f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SL4, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::RR4, tmp & 0x0f); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::TL4, tmp); tmp = propCtr.readUint8(envCsr++); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::ML4, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(envNum, FMEnvelopeParameter::SSGEG4, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); } /* LFO */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { fm->setLFOEnabled(false); fm->setLFONumber(0x7f & tmp); } else { fm->setLFOEnabled(true); uint8_t orgLFONum = 0x7f & tmp; int lfoNum = instMan.lock()->findFirstFreePlainLFOFM(); if (lfoNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); fm->setLFONumber(lfoNum); size_t lfoCsr = getPropertyPositionForBTB(propCtr, 0x01, orgLFONum); tmp = propCtr.readUint8(lfoCsr++); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::FREQ, tmp >> 4); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::PMS, tmp & 0x0f); tmp = propCtr.readUint8(lfoCsr++); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::AMS, tmp & 0x0f); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = propCtr.readUint8(lfoCsr++); instMan.lock()->setLFOFMParameter(lfoNum, FMLFOParameter::Count, tmp); } } /* Operator sequence */ uint8_t tmpCnt = 0; for (auto& param : FileIO::ENV_FM_PARAMS) { ++tmpCnt; uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { fm->setOperatorSequenceEnabled(param, false); fm->setOperatorSequenceNumber(param, 0x7f & tmp); } else { fm->setOperatorSequenceEnabled(param, true); uint8_t orgOpSeqNum = 0x7f & tmp; int opSeqNum = instMan.lock()->findFirstFreePlainOperatorSequenceFM(param); if (opSeqNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); fm->setOperatorSequenceNumber(param, opSeqNum); size_t opSeqCsr = getPropertyPositionForBTB(propCtr, 0x02 + tmpCnt, orgOpSeqNum); uint16_t seqLen = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; if (l == 0) instMan.lock()->setOperatorSequenceFMSequenceCommand(param, opSeqNum, 0, data, 0); else instMan.lock()->addOperatorSequenceFMSequenceCommand(param, opSeqNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(opSeqCsr)); opSeqCsr += 2; ends.push_back(propCtr.readUint16(opSeqCsr)); opSeqCsr += 2; times.push_back(propCtr.readUint8(opSeqCsr++)); } instMan.lock()->setOperatorSequenceFMLoops(param, opSeqNum, begins, ends, times); } switch (propCtr.readUint8(opSeqCsr++)) { case 0x00: // No release instMan.lock()->setOperatorSequenceFMRelease(param, opSeqNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(opSeqCsr); opSeqCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setOperatorSequenceFMRelease(param, opSeqNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setOperatorSequenceFMRelease(param, opSeqNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } } } /* Arpeggio */ { std::unordered_map tmpMap; std::unordered_map orgNumMap; tmpMap.emplace(FMOperatorType::All, instCtr.readUint8(instCsr)); instCsr += 3; tmpMap.emplace(FMOperatorType::Op1, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op2, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op3, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op4, instCtr.readUint8(instCsr)); instCsr -= 5; for (auto& pair : tmpMap) { if (0x80 & pair.second) { fm->setArpeggioEnabled(pair.first, false); fm->setArpeggioNumber(pair.first, 0x7f & pair.second); } else { fm->setArpeggioEnabled(pair.first, true); uint8_t orgArpNum = 0x7f & pair.second; auto it = orgNumMap.find(orgArpNum); if (it == orgNumMap.end()) { // Make new property orgNumMap.emplace(orgArpNum, pair.first); int arpNum = instMan.lock()->findFirstFreePlainArpeggioFM(); if (arpNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); fm->setArpeggioNumber(pair.first, arpNum); size_t arpCsr = getPropertyPositionForBTB(propCtr, 0x28, orgArpNum); uint16_t seqLen = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(arpCsr); arpCsr += 2; if (l == 0) instMan.lock()->setArpeggioFMSequenceCommand(arpNum, 0, data, 0); else instMan.lock()->addArpeggioFMSequenceCommand(arpNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(arpCsr); arpCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(arpCsr)); arpCsr += 2; ends.push_back(propCtr.readUint16(arpCsr)); arpCsr += 2; times.push_back(propCtr.readUint8(arpCsr++)); } instMan.lock()->setArpeggioFMLoops(arpNum, begins, ends, times); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // No release instMan.lock()->setArpeggioFMRelease(arpNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(arpCsr); arpCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioFMRelease(arpNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioFMRelease(arpNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioFMType(arpNum, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioFMType(arpNum, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioFMType(arpNum, SequenceType::RELATIVE_SEQUENCE); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioFMType(arpNum, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Bank); } } } else { // Use registered property fm->setArpeggioNumber(pair.first, fm->getArpeggioNumber(it->second)); } } } } /* Pitch */ { std::unordered_map tmpMap; std::unordered_map orgNumMap; tmpMap.emplace(FMOperatorType::All, instCtr.readUint8(instCsr)); instCsr += 6; tmpMap.emplace(FMOperatorType::Op1, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op2, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op3, instCtr.readUint8(instCsr++)); tmpMap.emplace(FMOperatorType::Op4, instCtr.readUint8(instCsr)); instCsr -= 8; for (auto& pair : tmpMap) { if (0x80 & pair.second) { fm->setPitchEnabled(pair.first, false); fm->setPitchNumber(pair.first, 0x7f & pair.second); } else { fm->setPitchEnabled(pair.first, true); uint8_t orgPtNum = 0x7f & pair.second; auto it = orgNumMap.find(orgPtNum); if (it == orgNumMap.end()) { // Make new property orgNumMap.emplace(orgPtNum, pair.first); int ptNum = instMan.lock()->findFirstFreePlainPitchFM(); if (ptNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); fm->setPitchNumber(pair.first, ptNum); size_t ptCsr = getPropertyPositionForBTB(propCtr, 0x29, orgPtNum); uint16_t seqLen = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(ptCsr); ptCsr += 2; if (l == 0) instMan.lock()->setPitchFMSequenceCommand(ptNum, 0, data, 0); else instMan.lock()->addPitchFMSequenceCommand(ptNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(ptCsr); ptCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(ptCsr)); ptCsr += 2; ends.push_back(propCtr.readUint16(ptCsr)); ptCsr += 2; times.push_back(propCtr.readUint8(ptCsr++)); } instMan.lock()->setPitchFMLoops(ptNum, begins, ends, times); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // No release instMan.lock()->setPitchFMRelease(ptNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(ptCsr); ptCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchFMRelease(ptNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchFMRelease(ptNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // Absolute instMan.lock()->setPitchFMType(ptNum, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchFMType(ptNum, SequenceType::RELATIVE_SEQUENCE); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchFMType(ptNum, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Bank); } } } else { // Use registered property fm->setPitchNumber(pair.first, fm->getPitchNumber(it->second)); } } } } /* Envelope reset */ { uint8_t tmp = instCtr.readUint8(instCsr); fm->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); fm->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); fm->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); fm->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); fm->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); } return fm; } case 0x01: // SSG { auto ssg = new InstrumentSSG(instNum, name, instMan.lock().get()); /* Wave form */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setWaveFormEnabled(false); ssg->setWaveFormNumber(0x7f & tmp); } else { ssg->setWaveFormEnabled(true); uint8_t orgWfNum = 0x7f & tmp; int wfNum = instMan.lock()->findFirstFreePlainWaveFormSSG(); if (wfNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); ssg->setWaveFormNumber(wfNum); size_t wfCsr = getPropertyPositionForBTB(propCtr, 0x30, orgWfNum); uint16_t seqLen = propCtr.readUint16(wfCsr); wfCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(wfCsr); wfCsr += 2; int32_t subdata; subdata = propCtr.readInt32(wfCsr); wfCsr += 4; if (l == 0) instMan.lock()->setWaveFormSSGSequenceCommand(wfNum, 0, data, subdata); else instMan.lock()->addWaveFormSSGSequenceCommand(wfNum, data, subdata); } uint16_t loopCnt = propCtr.readUint16(wfCsr); wfCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(wfCsr)); wfCsr += 2; ends.push_back(propCtr.readUint16(wfCsr)); wfCsr += 2; times.push_back(propCtr.readUint8(wfCsr++)); } instMan.lock()->setWaveFormSSGLoops(wfNum, begins, ends, times); } switch (propCtr.readUint8(wfCsr++)) { case 0x00: // No release instMan.lock()->setWaveFormSSGRelease(wfNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(wfCsr); wfCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setWaveFormSSGRelease(wfNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setWaveFormSSGRelease(wfNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } } } /* Tone/Noise */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setToneNoiseEnabled(false); ssg->setToneNoiseNumber(0x7f & tmp); } else { ssg->setToneNoiseEnabled(true); uint8_t orgTnNum = 0x7f & tmp; int tnNum = instMan.lock()->findFirstFreePlainToneNoiseSSG(); if (tnNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); ssg->setToneNoiseNumber(tnNum); size_t tnCsr = getPropertyPositionForBTB(propCtr, 0x31, orgTnNum); uint16_t seqLen = propCtr.readUint16(tnCsr); tnCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(tnCsr); tnCsr += 2; if (bankVersion < Version::toBCD(1, 0, 1)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (l == 0) instMan.lock()->setToneNoiseSSGSequenceCommand(tnNum, 0, data, 0); else instMan.lock()->addToneNoiseSSGSequenceCommand(tnNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(tnCsr); tnCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(tnCsr)); tnCsr += 2; ends.push_back(propCtr.readUint16(tnCsr)); tnCsr += 2; times.push_back(propCtr.readUint8(tnCsr++)); } instMan.lock()->setToneNoiseSSGLoops(tnNum, begins, ends, times); } switch (propCtr.readUint8(tnCsr++)) { case 0x00: // No release instMan.lock()->setToneNoiseSSGRelease(tnNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(tnCsr); tnCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setToneNoiseSSGRelease(tnNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setToneNoiseSSGRelease(tnNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } } } /* Envelope */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setEnvelopeEnabled(false); ssg->setEnvelopeNumber(0x7f & tmp); } else { ssg->setEnvelopeEnabled(true); uint8_t orgEnvNum = 0x7f & tmp; int envNum = instMan.lock()->findFirstFreePlainEnvelopeSSG(); if (envNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); ssg->setEnvelopeNumber(envNum); size_t envCsr = getPropertyPositionForBTB(propCtr, 0x32, orgEnvNum); uint16_t seqLen = propCtr.readUint16(envCsr); envCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(envCsr); envCsr += 2; int32_t subdata; subdata = propCtr.readInt32(envCsr); envCsr += 4; if (l == 0) instMan.lock()->setEnvelopeSSGSequenceCommand(envNum, 0, data, subdata); else instMan.lock()->addEnvelopeSSGSequenceCommand(envNum, data, subdata); } uint16_t loopCnt = propCtr.readUint16(envCsr); envCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(envCsr)); envCsr += 2; ends.push_back(propCtr.readUint16(envCsr)); envCsr += 2; times.push_back(propCtr.readUint8(envCsr++)); } instMan.lock()->setEnvelopeSSGLoops(envNum, begins, ends, times); } switch (propCtr.readUint8(envCsr++)) { case 0x00: // No release instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::NoRelease, -1); break; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::NoRelease, -1); break; } case 0x02: // Absolute { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::AbsoluteRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::NoRelease, -1); break; } case 0x03: // Relative { uint16_t pos = propCtr.readUint16(envCsr); envCsr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::RelativeRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(envNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } } } /* Arpeggio */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setArpeggioEnabled(false); ssg->setArpeggioNumber(0x7f & tmp); } else { ssg->setArpeggioEnabled(true); uint8_t orgArpNum = 0x7f & tmp; int arpNum = instMan.lock()->findFirstFreePlainArpeggioSSG(); if (arpNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); ssg->setArpeggioNumber(arpNum); size_t arpCsr = getPropertyPositionForBTB(propCtr, 0x33, orgArpNum); uint16_t seqLen = propCtr.readUint16(arpCsr); arpCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(arpCsr); arpCsr += 2; if (l == 0) instMan.lock()->setArpeggioSSGSequenceCommand(arpNum, 0, data, 0); else instMan.lock()->addArpeggioSSGSequenceCommand(arpNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(arpCsr); arpCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(arpCsr)); arpCsr += 2; ends.push_back(propCtr.readUint16(arpCsr)); arpCsr += 2; times.push_back(propCtr.readUint8(arpCsr++)); } instMan.lock()->setArpeggioSSGLoops(arpNum, begins, ends, times); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // No release instMan.lock()->setArpeggioSSGRelease(arpNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(arpCsr); arpCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioSSGRelease(arpNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioSSGRelease(arpNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } switch (propCtr.readUint8(arpCsr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioSSGType(arpNum, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioSSGType(arpNum, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioSSGType(arpNum, SequenceType::RELATIVE_SEQUENCE); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioSSGType(arpNum, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Bank); } } } } /* Pitch */ { uint8_t tmp = instCtr.readUint8(instCsr++); if (0x80 & tmp) { ssg->setPitchEnabled(false); ssg->setPitchNumber(0x7f & tmp); } else { ssg->setPitchEnabled(true); uint8_t orgPtNum = 0x7f & tmp; int ptNum = instMan.lock()->findFirstFreePlainPitchSSG(); if (ptNum == -1) throw FileCorruptionError(FileIO::FileType::Bank); ssg->setPitchNumber(ptNum); size_t ptCsr = getPropertyPositionForBTB(propCtr, 0x34, orgPtNum); uint16_t seqLen = propCtr.readUint16(ptCsr); ptCsr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = propCtr.readUint16(ptCsr); ptCsr += 2; if (l == 0) instMan.lock()->setPitchSSGSequenceCommand(ptNum, 0, data, 0); else instMan.lock()->addPitchSSGSequenceCommand(ptNum, data, 0); } uint16_t loopCnt = propCtr.readUint16(ptCsr); ptCsr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(propCtr.readUint16(ptCsr)); ptCsr += 2; ends.push_back(propCtr.readUint16(ptCsr)); ptCsr += 2; times.push_back(propCtr.readUint8(ptCsr++)); } instMan.lock()->setPitchSSGLoops(ptNum, begins, ends, times); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // No release instMan.lock()->setPitchSSGRelease(ptNum, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = propCtr.readUint16(ptCsr); ptCsr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchSSGRelease(ptNum, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchSSGRelease(ptNum, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Bank); } switch (propCtr.readUint8(ptCsr++)) { case 0x00: // Absolute instMan.lock()->setPitchSSGType(ptNum, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchSSGType(ptNum, SequenceType::RELATIVE_SEQUENCE); break; default: if (bankVersion < Version::toBCD(1, 0, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchSSGType(ptNum, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Bank); } } } } return ssg; } default: throw FileCorruptionError(FileIO::FileType::Bank); } } BambooTracker-0.3.5/BambooTracker/io/instrument_io.hpp000066400000000000000000000045101362177441300227740ustar00rootroot00000000000000#pragma once #include #include #include "instruments_manager.hpp" #include "binary_container.hpp" #include "format/wopn_file.h" class InstrumentIO { public: static void saveInstrument(BinaryContainer& ctr, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadInstrument(BinaryContainer& ctr, std::string path, std::weak_ptr instMan, int instNum); private: static AbstractInstrument* loadBTIFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadDMPFile(BinaryContainer& ctr,std::string path, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadTFIFile(BinaryContainer& ctr,std::string path, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadVGIFile(BinaryContainer& ctr,std::string path, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadOPNIFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadY12File(BinaryContainer& ctr,std::string path, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadINSFile(BinaryContainer& ctr, std::weak_ptr instMan, int instNum); public: static AbstractInstrument* loadWOPNInstrument(const WOPNInstrument &srcInst, std::weak_ptr instMan, int instNum); static AbstractInstrument* loadBTBInstrument(BinaryContainer instCtr, BinaryContainer propCtr, std::weak_ptr instMan, int instNum, uint32_t bankVersion); private: static size_t loadInstrumentPropertyOperatorSequenceForInstrument( FMEnvelopeParameter param, size_t instMemCsr, std::weak_ptr instMan, BinaryContainer& ctr, InstrumentFM* inst, int idx, uint32_t version); static size_t getPropertyPositionForBTB(const BinaryContainer& propCtr, uint8_t subsecType, uint8_t index); static int convertTFIVGMDT(int dt); private: InstrumentIO(); }; BambooTracker-0.3.5/BambooTracker/io/io_handlers.hpp000066400000000000000000000001711362177441300223630ustar00rootroot00000000000000#pragma once #include "module_io.hpp" #include "instrument_io.hpp" #include "bank_io.hpp" #include "export_handler.hpp" BambooTracker-0.3.5/BambooTracker/io/module_io.cpp000066400000000000000000002276301362177441300220560ustar00rootroot00000000000000#include "module_io.hpp" #include #include "version.hpp" #include "file_io_error.hpp" #include "file_io.hpp" #include "pitch_converter.hpp" #include "effect.hpp" ModuleIO::ModuleIO() {} void ModuleIO::saveModule(BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) { ctr.appendString("BambooTrackerMod"); size_t eofOfs = ctr.size(); ctr.appendUint32(0); // Dummy EOF offset uint32_t fileVersion = Version::ofModuleFileInBCD(); ctr.appendUint32(fileVersion); /***** Module section *****/ ctr.appendString("MODULE "); size_t modOfs = ctr.size(); ctr.appendUint32(0); // Dummy module section offset std::string modTitle = mod.lock()->getTitle(); ctr.appendUint32(modTitle.length()); if (!modTitle.empty()) ctr.appendString(modTitle); std::string author = mod.lock()->getAuthor(); ctr.appendUint32(author.length()); if (!author.empty()) ctr.appendString(author); std::string copyright = mod.lock()->getCopyright(); ctr.appendUint32(copyright.length()); if (!copyright.empty()) ctr.appendString(copyright); std::string comment = mod.lock()->getComment(); ctr.appendUint32(comment.length()); if (!comment.empty()) ctr.appendString(comment); ctr.appendUint32(mod.lock()->getTickFrequency()); ctr.appendUint32(mod.lock()->getStepHighlight1Distance()); ctr.appendUint32(mod.lock()->getStepHighlight2Distance()); MixerType mixType = mod.lock()->getMixerType(); ctr.appendUint8(static_cast(mixType)); if (mixType == MixerType::CUSTOM) { ctr.appendInt8(static_cast(mod.lock()->getCustomMixerFMLevel() * 10)); ctr.appendInt8(static_cast(mod.lock()->getCustomMixerSSGLevel() * 10)); } ctr.writeUint32(modOfs, ctr.size() - modOfs); /***** Instrument section *****/ ctr.appendString("INSTRMNT"); size_t instOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument section offset std::vector instIdcs = instMan.lock()->getEntriedInstrumentIndices(); ctr.appendUint8(static_cast(instIdcs.size())); for (auto& idx : instIdcs) { if (std::shared_ptr inst = instMan.lock()->getInstrumentSharedPtr(idx)) { ctr.appendUint8(static_cast(inst->getNumber())); size_t iOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument block offset std::string name = inst->getName(); ctr.appendUint32(name.length()); if (!name.empty()) ctr.appendString(name); switch (inst->getSoundSource()) { case SoundSource::FM: { ctr.appendUint8(0x00); auto instFM = std::dynamic_pointer_cast(inst); ctr.appendUint8(static_cast(instFM->getEnvelopeNumber())); uint8_t tmp = static_cast(instFM->getLFONumber()); ctr.appendUint8(instFM->getLFOEnabled() ? tmp : (0x80 | tmp)); for (auto& param : FileIO::ENV_FM_PARAMS) { tmp = static_cast(instFM->getOperatorSequenceNumber(param)); ctr.appendUint8(instFM->getOperatorSequenceEnabled(param) ? tmp : (0x80 | tmp)); } tmp = static_cast(instFM->getArpeggioNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getArpeggioEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getPitchNumber(FMOperatorType::All)); ctr.appendUint8(instFM->getPitchEnabled(FMOperatorType::All) ? tmp : (0x80 | tmp)); tmp = static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::All)) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op1) << 1) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op2) << 2) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op3) << 3) | static_cast(instFM->getEnvelopeResetEnabled(FMOperatorType::Op4) << 4); ctr.appendUint8(tmp); for (auto& type : FileIO::OP_FM_TYPES) { tmp = static_cast(instFM->getArpeggioNumber(type)); ctr.appendUint8(instFM->getArpeggioEnabled(type) ? tmp : (0x80 | tmp)); } for (auto& type : FileIO::OP_FM_TYPES) { tmp = static_cast(instFM->getPitchNumber(type)); ctr.appendUint8(instFM->getPitchEnabled(type) ? tmp : (0x80 | tmp)); } break; } case SoundSource::SSG: { ctr.appendUint8(0x01); auto instSSG = std::dynamic_pointer_cast(inst); uint8_t tmp = static_cast(instSSG->getWaveFormNumber()); ctr.appendUint8(instSSG->getWaveFormEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getToneNoiseNumber()); ctr.appendUint8(instSSG->getToneNoiseEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getEnvelopeNumber()); ctr.appendUint8(instSSG->getEnvelopeEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getArpeggioNumber()); ctr.appendUint8(instSSG->getArpeggioEnabled() ? tmp : (0x80 | tmp)); tmp = static_cast(instSSG->getPitchNumber()); ctr.appendUint8(instSSG->getPitchEnabled() ? tmp : (0x80 | tmp)); break; } case SoundSource::DRUM: break; } ctr.writeUint32(iOfs, ctr.size() - iOfs); } } ctr.writeUint32(instOfs, ctr.size() - instOfs); /***** Instrument property section *****/ ctr.appendString("INSTPROP"); size_t instPropOfs = ctr.size(); ctr.appendUint32(0); // Dummy instrument property section offset // FM envelope std::vector envFMIdcs = instMan.lock()->getEnvelopeFMEntriedIndices(); if (!envFMIdcs.empty()) { ctr.appendUint8(0x00); ctr.appendUint8(static_cast(envFMIdcs.size())); for (auto& idx : envFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AL) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::FB)); ctr.appendUint8(tmp); // Operator 1 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 0); tmp = static_cast((tmp << 5)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT1) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL1) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR1)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL1)); ctr.appendUint8(tmp); int tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG1); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML1)); ctr.appendUint8(tmp); // Operator 2 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 1); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT2) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL2) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR2)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL2)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG2); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML2)); ctr.appendUint8(tmp); // Operator 3 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 2); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT3) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL3) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR3)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL3)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG3); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML3)); ctr.appendUint8(tmp); // Operator 4 tmp = instMan.lock()->getEnvelopeFMOperatorEnabled(idx, 3); tmp = static_cast(tmp << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::AR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::KS4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::DT4) << 5) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SL4) << 4) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::RR4)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::TL4)); ctr.appendUint8(tmp); tmp2 = instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG4); tmp = ((tmp2 == -1) ? 0x80 : static_cast(tmp2 << 4)) | static_cast(instMan.lock()->getEnvelopeFMParameter(idx, FMEnvelopeParameter::ML4)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM LFO std::vector lfoFMIdcs = instMan.lock()->getLFOFMEntriedIndices(); if (!lfoFMIdcs.empty()) { ctr.appendUint8(0x01); ctr.appendUint8(static_cast(lfoFMIdcs.size())); for (auto& idx : lfoFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint8(0); // Dummy offset uint8_t tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::FREQ) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::PMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM4) << 7) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM3) << 6) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM2) << 5) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AM1) << 4) | static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::AMS)); ctr.appendUint8(tmp); tmp = static_cast(instMan.lock()->getLFOFMparameter(idx, FMLFOParameter::Count)); ctr.appendUint8(tmp); ctr.writeUint8(ofs, static_cast(ctr.size() - ofs)); } } // FM envelope parameter for (size_t i = 0; i < 38; ++i) { std::vector idcs = instMan.lock()->getOperatorSequenceFMEntriedIndices(FileIO::ENV_FM_PARAMS[i]); if (!idcs.empty()) { ctr.appendUint8(0x02 + static_cast(i)); ctr.appendUint8(static_cast(idcs.size())); for (auto& idx : idcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getOperatorSequenceFMSequence(FileIO::ENV_FM_PARAMS[i], idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getOperatorSequenceFMLoops(FileIO::ENV_FM_PARAMS[i], idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getOperatorSequenceFMRelease(FileIO::ENV_FM_PARAMS[i], idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } } // FM arpeggio std::vector arpFMIdcs = instMan.lock()->getArpeggioFMEntriedIndices(); if (!arpFMIdcs.empty()) { ctr.appendUint8(0x28); ctr.appendUint8(static_cast(arpFMIdcs.size())); for (auto& idx : arpFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioFMLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioFMRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioFMType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // FM pitch std::vector ptFMIdcs = instMan.lock()->getPitchFMEntriedIndices(); if (!ptFMIdcs.empty()) { ctr.appendUint8(0x29); ctr.appendUint8(static_cast(ptFMIdcs.size())); for (auto& idx : ptFMIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchFMSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchFMLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchFMRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchFMType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG wave form std::vector wfSSGIdcs = instMan.lock()->getWaveFormSSGEntriedIndices(); if (!wfSSGIdcs.empty()) { ctr.appendUint8(0x30); ctr.appendUint8(static_cast(wfSSGIdcs.size())); for (auto& idx : wfSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getWaveFormSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getWaveFormSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getWaveFormSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG tone/noise std::vector tnSSGIdcs = instMan.lock()->getToneNoiseSSGEntriedIndices(); if (!tnSSGIdcs.empty()) { ctr.appendUint8(0x31); ctr.appendUint8(static_cast(tnSSGIdcs.size())); for (auto& idx : tnSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getToneNoiseSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getToneNoiseSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getToneNoiseSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG envelope std::vector envSSGIdcs = instMan.lock()->getEnvelopeSSGEntriedIndices(); if (!envSSGIdcs.empty()) { ctr.appendUint8(0x32); ctr.appendUint8(static_cast(envSSGIdcs.size())); for (auto& idx : envSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getEnvelopeSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); ctr.appendInt32(static_cast(com.data)); } auto loop = instMan.lock()->getEnvelopeSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getEnvelopeSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } ctr.appendUint8(0); // Skip sequence type ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG arpeggio std::vector arpSSGIdcs = instMan.lock()->getArpeggioSSGEntriedIndices(); if (!arpSSGIdcs.empty()) { ctr.appendUint8(0x33); ctr.appendUint8(static_cast(arpSSGIdcs.size())); for (auto& idx : arpSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getArpeggioSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getArpeggioSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getArpeggioSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getArpeggioSSGType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::FIXED_SEQUENCE: ctr.appendUint8(0x01); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } // SSG pitch std::vector ptSSGIdcs = instMan.lock()->getPitchSSGEntriedIndices(); if (!ptSSGIdcs.empty()) { ctr.appendUint8(0x34); ctr.appendUint8(static_cast(ptSSGIdcs.size())); for (auto& idx : ptSSGIdcs) { ctr.appendUint8(static_cast(idx)); size_t ofs = ctr.size(); ctr.appendUint16(0); // Dummy offset auto seq = instMan.lock()->getPitchSSGSequence(idx); ctr.appendUint16(static_cast(seq.size())); for (auto& com : seq) { ctr.appendUint16(static_cast(com.type)); } auto loop = instMan.lock()->getPitchSSGLoops(idx); ctr.appendUint16(static_cast(loop.size())); for (auto& l : loop) { ctr.appendUint16(static_cast(l.begin)); ctr.appendUint16(static_cast(l.end)); ctr.appendUint8(static_cast(l.times)); } auto release = instMan.lock()->getPitchSSGRelease(idx); switch (release.type) { case ReleaseType::NoRelease: ctr.appendUint8(0x00); // If release.type is NO_RELEASE, then release.begin == -1 so omit to save it. break; case ReleaseType::FixedRelease: ctr.appendUint8(0x01); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::AbsoluteRelease: ctr.appendUint8(0x02); ctr.appendUint16(static_cast(release.begin)); break; case ReleaseType::RelativeRelease: ctr.appendUint8(0x03); ctr.appendUint16(static_cast(release.begin)); break; } switch (instMan.lock()->getPitchSSGType(idx)) { case SequenceType::ABSOLUTE_SEQUENCE: ctr.appendUint8(0x00); break; case SequenceType::RELATIVE_SEQUENCE: ctr.appendUint8(0x02); break; default: break; } ctr.writeUint16(ofs, static_cast(ctr.size() - ofs)); } } ctr.writeUint32(instPropOfs, ctr.size() - instPropOfs); /***** Groove section *****/ ctr.appendString("GROOVE "); size_t grooveOfs = ctr.size(); ctr.appendUint32(0); // Dummy groove section offset size_t grooveCnt = mod.lock()->getGrooveCount(); ctr.appendUint8(static_cast(grooveCnt - 1)); for (size_t i = 0; i < grooveCnt; ++i) { ctr.appendUint8(static_cast(i)); auto seq = mod.lock()->getGroove(static_cast(i)).getSequence(); ctr.appendUint8(static_cast(seq.size())); for (auto& g : seq) { ctr.appendUint8(static_cast(g)); } } ctr.writeUint32(grooveOfs, ctr.size() - grooveOfs); /***** Song section *****/ ctr.appendString("SONG "); size_t songSecOfs = ctr.size(); ctr.appendUint32(0); // Dummy song section offset size_t songCnt = mod.lock()->getSongCount(); ctr.appendUint8(static_cast(songCnt)); // Song for (size_t i = 0; i < songCnt; ++i) { ctr.appendUint8(static_cast(i)); size_t songOfs = ctr.size(); ctr.appendUint32(0); // Dummy song block offset auto& sng = mod.lock()->getSong(static_cast(i)); std::string title = sng.getTitle(); ctr.appendUint32(title.length()); if (!title.empty()) ctr.appendString(title); ctr.appendUint32(static_cast(sng.getTempo())); uint8_t tmp = static_cast(sng.getGroove()); ctr.appendUint8(sng.isUsedTempo() ? (0x80 | tmp) : tmp); ctr.appendUint32(static_cast(sng.getSpeed())); ctr.appendUint8(static_cast(sng.getDefaultPatternSize()) - 1); auto style = sng.getStyle(); switch (style.type) { case SongType::Standard: ctr.appendUint8(0x00); break; case SongType::FM3chExpanded: ctr.appendUint8(0x01); break; default: throw FileOutputError(FileIO::FileType::Mod); } // Track for (auto& attrib : style.trackAttribs) { ctr.appendUint8(static_cast(attrib.number)); size_t trackOfs = ctr.size(); ctr.appendUint32(0); // Dummy track subblock offset auto& track = sng.getTrack(attrib.number); // Order size_t odrSize = track.getOrderSize(); ctr.appendUint8(static_cast(odrSize) - 1); for (size_t o = 0; o < odrSize; ++o) ctr.appendUint8(static_cast(track.getOrderData(static_cast(o)).patten)); ctr.appendUint8(static_cast(track.getEffectDisplayWidth())); // Pattern for (auto& idx : track.getEditedPatternIndices()) { ctr.appendUint8(static_cast(idx)); size_t ptnOfs = ctr.size(); ctr.appendUint32(0); // Dummy pattern subblock offset auto& pattern = track.getPattern(idx); // Step std::vector stepIdcs = pattern.getEditedStepIndices(); for (auto& sidx : stepIdcs) { ctr.appendUint8(static_cast(sidx)); size_t evFlagOfs = ctr.size(); ctr.appendUint16(0); // Dummy set event flag auto& step = pattern.getStep(sidx); uint16_t eventFlag = 0; int tmp = step.getNoteNumber(); if (tmp != -1) { eventFlag |= 0x0001; ctr.appendInt8(static_cast(tmp)); } tmp = step.getInstrumentNumber(); if (tmp != -1) { eventFlag |= 0x0002; ctr.appendUint8(static_cast(tmp)); } tmp = step.getVolume(); if (tmp != -1) { eventFlag |= 0x0004; ctr.appendUint8(static_cast(tmp)); } for (int i = 0; i < 4; ++i) { std::string tmpstr = step.getEffectID(i); if (tmpstr != "--") { eventFlag |= (0x0008 << (i << 1)); ctr.appendString(tmpstr); } tmp = step.getEffectValue(i); if (tmp != -1) { eventFlag |= (0x0010 << (i << 1)); ctr.appendUint8(static_cast(tmp)); } } ctr.writeUint16(evFlagOfs, eventFlag); } ctr.writeUint32(ptnOfs, ctr.size() - ptnOfs); } ctr.writeUint32(trackOfs, ctr.size() - trackOfs); } ctr.writeUint32(songOfs, ctr.size() - songOfs); } ctr.writeUint32(songSecOfs, ctr.size() - songSecOfs); ctr.writeUint32(eofOfs, ctr.size() - eofOfs); } void ModuleIO::loadModule(BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan) { size_t globCsr = 0; if (ctr.readString(globCsr, 16) != "BambooTrackerMod") throw FileCorruptionError(FileIO::FileType::Mod); globCsr += 16; size_t eofOfs = ctr.readUint32(globCsr); size_t eof = globCsr + eofOfs; globCsr += 4; size_t fileVersion = ctr.readUint32(globCsr); if (fileVersion > Version::ofModuleFileInBCD()) throw FileVersionError(FileIO::FileType::Mod); globCsr += 4; while (globCsr < eof) { if (ctr.readString(globCsr, 8) == "MODULE ") globCsr = loadModuleSectionInModule(mod, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "INSTRMNT") globCsr = loadInstrumentSectionInModule(instMan, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "INSTPROP") globCsr = loadInstrumentPropertySectionInModule(instMan, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "GROOVE ") globCsr = loadGrooveSectionInModule(mod, ctr, globCsr + 8, fileVersion); else if (ctr.readString(globCsr, 8) == "SONG ") globCsr = loadSongSectionInModule(mod, ctr, globCsr + 8, fileVersion); else throw FileCorruptionError(FileIO::FileType::Mod); } } size_t ModuleIO::loadModuleSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version) { size_t modOfs = ctr.readUint32(globCsr); size_t modCsr = globCsr + 4; size_t modTitleLen = ctr.readUint32(modCsr); modCsr += 4; if (modTitleLen > 0) { mod.lock()->setTitle(ctr.readString(modCsr, modTitleLen)); modCsr += modTitleLen; } size_t modAuthorLen = ctr.readUint32(modCsr); modCsr += 4; if (modAuthorLen > 0) { mod.lock()->setAuthor(ctr.readString(modCsr, modAuthorLen)); modCsr += modAuthorLen; } size_t modCopyrightLen = ctr.readUint32(modCsr); modCsr += 4; if (modCopyrightLen > 0) { mod.lock()->setCopyright(ctr.readString(modCsr, modCopyrightLen)); modCsr += modCopyrightLen; } size_t modCommentLen = ctr.readUint32(modCsr); modCsr += 4; if (modCommentLen > 0) { mod.lock()->setComment(ctr.readString(modCsr, modCommentLen)); modCsr += modCommentLen; } mod.lock()->setTickFrequency(ctr.readUint32(modCsr)); modCsr += 4; mod.lock()->setStepHighlight1Distance(ctr.readUint32(modCsr)); modCsr += 4; if (version >= Version::toBCD(1, 0, 3)) { mod.lock()->setStepHighlight2Distance(ctr.readUint32(modCsr)); modCsr += 4; } else { mod.lock()->setStepHighlight2Distance(mod.lock()->getStepHighlight1Distance() * 4); } if (version >= Version::toBCD(1, 3, 0)) { auto mixType = static_cast(ctr.readUint8(modCsr++)); mod.lock()->setMixerType(mixType); if (mixType == MixerType::CUSTOM) { mod.lock()->setCustomMixerFMLevel(ctr.readInt8(modCsr++) / 10.0); mod.lock()->setCustomMixerSSGLevel(ctr.readInt8(modCsr++) / 10.0); } } else { mod.lock()->setMixerType(MixerType::UNSPECIFIED); } return globCsr + modOfs; } size_t ModuleIO::loadInstrumentSectionInModule(std::weak_ptr instMan, BinaryContainer& ctr, size_t globCsr, uint32_t version) { (void)version; size_t instOfs = ctr.readUint32(globCsr); size_t instCsr = globCsr + 4; uint8_t instCnt = ctr.readUint8(instCsr); instCsr += 1; for (uint8_t i = 0; i < instCnt; ++i) { uint8_t idx = ctr.readUint8(instCsr); instCsr += 1; size_t iOfs = ctr.readUint32(instCsr); size_t iCsr = instCsr + 4; size_t nameLen = ctr.readUint32(iCsr); iCsr += 4; std::string name = u8""; if (nameLen > 0) { name = ctr.readString(iCsr, nameLen); iCsr += nameLen; } switch (ctr.readUint8(iCsr++)) { case 0x00: // FM { InstrumentFM* instFM = new InstrumentFM(idx, name, instMan.lock().get()); instFM->setEnvelopeNumber(ctr.readUint8(iCsr)); iCsr += 1; uint8_t tmp = ctr.readUint8(iCsr); instFM->setLFOEnabled((0x80 & tmp) ? false : true); instFM->setLFONumber(0x7f & tmp); iCsr += 1; for (auto& param : FileIO::ENV_FM_PARAMS) { tmp = ctr.readUint8(iCsr); instFM->setOperatorSequenceEnabled(param, (0x80 & tmp) ? false : true); instFM->setOperatorSequenceNumber(param, 0x7f & tmp); iCsr += 1; } tmp = ctr.readUint8(iCsr); instFM->setArpeggioEnabled(FMOperatorType::All, (0x80 & tmp) ? false : true); instFM->setArpeggioNumber(FMOperatorType::All, 0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instFM->setPitchEnabled(FMOperatorType::All, (0x80 & tmp) ? false : true); instFM->setPitchNumber(FMOperatorType::All, 0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instFM->setEnvelopeResetEnabled(FMOperatorType::All, (tmp & 0x01)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op1, (tmp & 0x02)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op2, (tmp & 0x04)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op3, (tmp & 0x08)); instFM->setEnvelopeResetEnabled(FMOperatorType::Op4, (tmp & 0x10)); iCsr += 1; if (version >= Version::toBCD(1, 1, 0)) { for (auto& t : FileIO::OP_FM_TYPES) { tmp = ctr.readUint8(iCsr); instFM->setArpeggioEnabled(t, (0x80 & tmp) ? false : true); instFM->setArpeggioNumber(t, 0x7f & tmp); iCsr += 1; } for (auto& t : FileIO::OP_FM_TYPES) { tmp = ctr.readUint8(iCsr); instFM->setPitchEnabled(t, (0x80 & tmp) ? false : true); instFM->setPitchNumber(t, 0x7f & tmp); iCsr += 1; } } instMan.lock()->addInstrument(std::unique_ptr(instFM)); break; } case 0x01: // SSG { InstrumentSSG* instSSG = new InstrumentSSG(idx, name, instMan.lock().get()); uint8_t tmp = ctr.readUint8(iCsr); instSSG->setWaveFormEnabled((0x80 & tmp) ? false : true); instSSG->setWaveFormNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setToneNoiseEnabled((0x80 & tmp) ? false : true); instSSG->setToneNoiseNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setEnvelopeEnabled((0x80 & tmp) ? false : true); instSSG->setEnvelopeNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setArpeggioEnabled((0x80 & tmp) ? false : true); instSSG->setArpeggioNumber(0x7f & tmp); iCsr += 1; tmp = ctr.readUint8(iCsr); instSSG->setPitchEnabled((0x80 & tmp) ? false : true); instSSG->setPitchNumber(0x7f & tmp); iCsr += 1; instMan.lock()->addInstrument(std::unique_ptr(instSSG)); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } instCsr += iOfs; } return globCsr + instOfs; } size_t ModuleIO::loadInstrumentPropertySectionInModule(std::weak_ptr instMan, BinaryContainer& ctr, size_t globCsr, uint32_t version) { size_t instPropOfs = ctr.readUint32(globCsr); size_t instPropCsr = globCsr + 4; globCsr += instPropOfs; while (instPropCsr < globCsr) { switch (ctr.readUint8(instPropCsr++)) { case 0x00: // FM envelope { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AL, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::FB, tmp & 0x0f); // Operator 1 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 0, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT1, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR1, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL1, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR1, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL1, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML1, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG1, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 2 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 1, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT2, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR2, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL2, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR2, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL2, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML2, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG2, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 3 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 2, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT3, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR3, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL3, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR3, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL3, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML3, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG3, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); // Operator 4 tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMOperatorEnabled(idx, 3, (0x20 & tmp) ? true : false); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::AR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::KS4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::DT4, tmp >> 5); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SR4, tmp & 0x1f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SL4, tmp >> 4); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::RR4, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::TL4, tmp); tmp = ctr.readUint8(csr++); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::ML4, tmp & 0x0f); instMan.lock()->setEnvelopeFMParameter(idx, FMEnvelopeParameter::SSGEG4, (tmp & 0x80) ? -1 : ((tmp >> 4) & 0x07)); instPropCsr += ofs; } break; } case 0x01: // FM LFO { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint8_t ofs = ctr.readUint8(instPropCsr); size_t csr = instPropCsr + 1; uint8_t tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::FREQ, tmp >> 4); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::PMS, tmp & 0x0f); tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AMS, tmp & 0x0f); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM1, (tmp & 0x10) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM2, (tmp & 0x20) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM3, (tmp & 0x40) ? true : false); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::AM4, (tmp & 0x80) ? true : false); tmp = ctr.readUint8(csr++); instMan.lock()->setLFOFMParameter(idx, FMLFOParameter::Count, tmp); instPropCsr += ofs; } break; } case 0x02: // FM AL { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::AL, instPropCsr, instMan, ctr, version); break; } case 0x03: // FM FB { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::FB, instPropCsr, instMan, ctr, version); break; } case 0x04: // FM AR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::AR1, instPropCsr, instMan, ctr, version); break; } case 0x05: // FM DR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DR1, instPropCsr, instMan, ctr, version); break; } case 0x06: // FM SR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SR1, instPropCsr, instMan, ctr, version); break; } case 0x07: // FM RR1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::RR1, instPropCsr, instMan, ctr, version); break; } case 0x08: // FM SL1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SL1, instPropCsr, instMan, ctr, version); break; } case 0x09: // FM TL1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::TL1, instPropCsr, instMan, ctr, version); break; } case 0x0a: // FM KS1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::KS1, instPropCsr, instMan, ctr, version); break; } case 0x0b: // FM ML1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::ML1, instPropCsr, instMan, ctr, version); break; } case 0x0c: // FM DT1 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DT1, instPropCsr, instMan, ctr, version); break; } case 0x0d: // FM AR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::AR2, instPropCsr, instMan, ctr, version); break; } case 0x0e: // FM DR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DR2, instPropCsr, instMan, ctr, version); break; } case 0x0f: // FM SR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SR2, instPropCsr, instMan, ctr, version); break; } case 0x10: // FM RR2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::RR2, instPropCsr, instMan, ctr, version); break; } case 0x11: // FM SL2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SL2, instPropCsr, instMan, ctr, version); break; } case 0x12: // FM TL2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::TL2, instPropCsr, instMan, ctr, version); break; } case 0x13: // FM KS2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::KS2, instPropCsr, instMan, ctr, version); break; } case 0x14: // FM ML2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::ML2, instPropCsr, instMan, ctr, version); break; } case 0x15: // FM DT2 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DT2, instPropCsr, instMan, ctr, version); break; } case 0x16: // FM AR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::AR3, instPropCsr, instMan, ctr, version); break; } case 0x17: // FM DR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DR3, instPropCsr, instMan, ctr, version); break; } case 0x18: // FM SR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SR3, instPropCsr, instMan, ctr, version); break; } case 0x19: // FM RR3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::RR3, instPropCsr, instMan, ctr, version); break; } case 0x1a: // FM SL3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SL3, instPropCsr, instMan, ctr, version); break; } case 0x1b: // FM TL3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::TL3, instPropCsr, instMan, ctr, version); break; } case 0x1c: // FM KS3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::KS3, instPropCsr, instMan, ctr, version); break; } case 0x1d: // FM ML3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::ML3, instPropCsr, instMan, ctr, version); break; } case 0x1e: // FM DT3 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DT3, instPropCsr, instMan, ctr, version); break; } case 0x1f: // FM AR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::AR4, instPropCsr, instMan, ctr, version); break; } case 0x20: // FM DR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DR4, instPropCsr, instMan, ctr, version); break; } case 0x21: // FM SR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SR4, instPropCsr, instMan, ctr, version); break; } case 0x22: // FM RR4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::RR4, instPropCsr, instMan, ctr, version); break; } case 0x23: // FM SL4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::SL4, instPropCsr, instMan, ctr, version); break; } case 0x24: // FM TL4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::TL4, instPropCsr, instMan, ctr, version); break; } case 0x25: // FM KS4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::KS4, instPropCsr, instMan, ctr, version); break; } case 0x26: // FM ML4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::ML4, instPropCsr, instMan, ctr, version); break; } case 0x27: // FM DT4 { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) instPropCsr += loadInstrumentPropertyOperatorSequence( FMEnvelopeParameter::DT4, instPropCsr, instMan, ctr, version); break; } case 0x28: // FM arpeggio { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setArpeggioFMSequenceCommand(idx, 0, data, 0); else instMan.lock()->addArpeggioFMSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setArpeggioFMLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioFMRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioFMType(idx, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioFMType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Mod); } } } instPropCsr += ofs; } break; } case 0x29: // FM pitch { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setPitchFMSequenceCommand(idx, 0, data, 0); else instMan.lock()->addPitchFMSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setPitchFMLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setPitchFMRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchFMRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchFMRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setPitchFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchFMType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchFMType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Mod); } } } instPropCsr += ofs; } break; } case 0x30: // SSG wave form { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) { if (data == 3) data = static_cast(SSGWaveFormType::SQM_TRIANGLE); else if (data == 4) data = static_cast(SSGWaveFormType::SQM_SAW); } int32_t subdata; if (version >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; if (subdata != -1) subdata = PitchConverter::getPitchSSGSquare(subdata); } if (l == 0) instMan.lock()->setWaveFormSSGSequenceCommand(idx, 0, data, subdata); else instMan.lock()->addWaveFormSSGSequenceCommand(idx, data, subdata); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setWaveFormSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setWaveFormSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x31: // SSG tone/noise { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 3, 1)) { if (data > 0) { uint16_t tmp = data - 1; data = tmp / 32 * 32 + (31 - tmp % 32) + 1; } } if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setToneNoiseSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addToneNoiseSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setToneNoiseSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setToneNoiseSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x32: // SSG envelope { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; int32_t subdata; if (version >= Version::toBCD(1, 2, 0)) { subdata = ctr.readInt32(csr); csr += 4; } else { subdata = ctr.readUint16(csr); csr += 2; } if (l == 0) instMan.lock()->setEnvelopeSSGSequenceCommand(idx, 0, data, subdata); else instMan.lock()->addEnvelopeSSGSequenceCommand(idx, data, subdata); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setEnvelopeSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } case 0x02: // Absolute { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::AbsoluteRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } case 0x03: // Relative { uint16_t pos = ctr.readUint16(csr); csr += 2; if (pos < seqLen) instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::RelativeRelease, pos); else instMan.lock()->setEnvelopeSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } instPropCsr += ofs; } break; } case 0x33: // SSG arpeggio { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setArpeggioSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addArpeggioSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setArpeggioSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setArpeggioSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setArpeggioSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x01: // Fixed instMan.lock()->setArpeggioSSGType(idx, SequenceType::FIXED_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setArpeggioSSGType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setArpeggioSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Mod); } } } instPropCsr += ofs; } break; } case 0x34: // SSG pitch { uint8_t cnt = ctr.readUint8(instPropCsr++); for (size_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(instPropCsr++); uint16_t ofs = ctr.readUint16(instPropCsr); size_t csr = instPropCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 0)) csr += 2; if (l == 0) instMan.lock()->setPitchSSGSequenceCommand(idx, 0, data, 0); else instMan.lock()->addPitchSSGSequenceCommand(idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setPitchSSGLoops(idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setPitchSSGRelease(idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug) // https://github.com/rerrahkr/BambooTracker/issues/11 if (pos < seqLen) instMan.lock()->setPitchSSGRelease(idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setPitchSSGRelease(idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { switch (ctr.readUint8(csr++)) { case 0x00: // Absolute instMan.lock()->setPitchSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; case 0x02: // Relative instMan.lock()->setPitchSSGType(idx, SequenceType::RELATIVE_SEQUENCE); break; default: if (version < Version::toBCD(1, 3, 2)) { // Recover deep clone bug // https://github.com/rerrahkr/BambooTracker/issues/170 instMan.lock()->setPitchSSGType(idx, SequenceType::ABSOLUTE_SEQUENCE); break; } else { throw FileCorruptionError(FileIO::FileType::Mod); } } } instPropCsr += ofs; } break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } } return globCsr; } size_t ModuleIO::loadInstrumentPropertyOperatorSequence(FMEnvelopeParameter param, size_t instMemCsr, std::weak_ptr instMan, BinaryContainer& ctr, uint32_t version) { uint8_t idx = ctr.readUint8(instMemCsr++); uint16_t ofs = ctr.readUint16(instMemCsr); size_t csr = instMemCsr + 2; uint16_t seqLen = ctr.readUint16(csr); csr += 2; for (uint16_t l = 0; l < seqLen; ++l) { uint16_t data = ctr.readUint16(csr); csr += 2; if (version < Version::toBCD(1, 2, 2)) csr += 2; if (l == 0) instMan.lock()->setOperatorSequenceFMSequenceCommand(param, idx, 0, data, 0); else instMan.lock()->addOperatorSequenceFMSequenceCommand(param, idx, data, 0); } uint16_t loopCnt = ctr.readUint16(csr); csr += 2; if (loopCnt > 0) { std::vector begins, ends, times; for (uint16_t l = 0; l < loopCnt; ++l) { begins.push_back(ctr.readUint16(csr)); csr += 2; ends.push_back(ctr.readUint16(csr)); csr += 2; times.push_back(ctr.readUint8(csr++)); } instMan.lock()->setOperatorSequenceFMLoops(param, idx, begins, ends, times); } switch (ctr.readUint8(csr++)) { case 0x00: // No release instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::NoRelease, -1); break; case 0x01: // Fixed { uint16_t pos = ctr.readUint16(csr); csr += 2; // Release point check (prevents a bug; see rerrahkr/BambooTracker issue #11) if (pos < seqLen) instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::FixedRelease, pos); else instMan.lock()->setOperatorSequenceFMRelease(param, idx, ReleaseType::NoRelease, -1); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } if (version >= Version::toBCD(1, 0, 1)) { ++csr; // Skip sequence type } return ofs + 1; } size_t ModuleIO::loadGrooveSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version) { (void)version; size_t grvOfs = ctr.readUint32(globCsr); size_t grvCsr = globCsr + 4; uint8_t cnt = ctr.readUint8(grvCsr++) + 1; for (uint8_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(grvCsr++); uint8_t seqLen = ctr.readUint8(grvCsr++); std::vector seq; for (uint8_t l = 0; l < seqLen; ++l) { seq.push_back(ctr.readUint8(grvCsr++)); } if (idx > 0) mod.lock()->addGroove(); mod.lock()->setGroove(idx, seq); } return globCsr + grvOfs; } size_t ModuleIO::loadSongSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version) { size_t songOfs = ctr.readUint32(globCsr); size_t songCsr = globCsr + 4; uint8_t cnt = ctr.readUint8(songCsr++); for (uint8_t i = 0; i < cnt; ++i) { uint8_t idx = ctr.readUint8(songCsr++); size_t sOfs = ctr.readUint32(songCsr); size_t scsr = songCsr + 4; songCsr += sOfs; size_t titleLen = ctr.readUint32(scsr); scsr += 4; std::string title = u8""; if (titleLen > 0) title = ctr.readString(scsr, titleLen); scsr += titleLen; uint32_t tempo = ctr.readUint32(scsr); scsr += 4; uint8_t groove = ctr.readUint8(scsr); scsr += 1; bool isTempo = (groove & 0x80) ? true : false; groove &= 0x7f; uint32_t speed = ctr.readUint32(scsr); scsr += 4; size_t ptnSize = ctr.readUint8(scsr) + 1; scsr += 1; switch (ctr.readUint8(scsr++)) { case 0x00: // Standard { mod.lock()->addSong(idx, SongType::Standard, title, isTempo, static_cast(tempo), groove, static_cast(speed), ptnSize); break; } case 0x01: // FM3ch expanded { mod.lock()->addSong(idx, SongType::FM3chExpanded, title, isTempo, static_cast(tempo), groove, static_cast(speed), ptnSize); break; } default: throw FileCorruptionError(FileIO::FileType::Mod); } auto& song = mod.lock()->getSong(idx); while (scsr < songCsr) { // Song uint8_t trackIdx = ctr.readUint8(scsr++); auto& track = song.getTrack(trackIdx); size_t trackOfs = ctr.readUint32(scsr); size_t trackEnd = scsr + trackOfs; size_t tcsr = scsr + 4; uint8_t odrLen = ctr.readUint8(tcsr++) + 1; for (uint8_t oi = 0; oi < odrLen; ++oi) { if (!oi) track.registerPatternToOrder(oi, ctr.readUint8(tcsr++)); else { track.insertOrderBelow(oi - 1); track.registerPatternToOrder(oi, ctr.readUint8(tcsr++)); } } if (version >= Version::toBCD(1, 2, 1)) { track.setEffectDisplayWidth(ctr.readUint8(tcsr++)); } SoundSource sndsrc = track.getAttribute().source; // Pattern while (tcsr < trackEnd) { uint8_t ptnIdx = ctr.readUint8(tcsr++); auto& pattern = track.getPattern(ptnIdx); size_t ptnOfs = ctr.readUint32(tcsr); size_t pcsr = tcsr + 4; tcsr += ptnOfs; // Step while (pcsr < tcsr) { uint32_t stepIdx = ctr.readUint8(pcsr++); auto& step = pattern.getStep(static_cast(stepIdx)); uint16_t eventFlag = ctr.readUint16(pcsr); pcsr += 2; if (eventFlag & 0x0001) { if (version >= Version::toBCD(1, 0, 2)) { step.setNoteNumber(ctr.readInt8(pcsr++)); } else { // Change FM octave (song type is only 0x00 before v1.0.2) int8_t nn = ctr.readInt8(pcsr++); if (trackIdx < 6 && 0 <= nn && nn < 84) step.setNoteNumber(nn + 12); else step.setNoteNumber(nn); } } if (eventFlag & 0x0002) step.setInstrumentNumber(ctr.readUint8(pcsr++)); if (eventFlag & 0x0004) step.setVolume(ctr.readUint8(pcsr++)); EffectType efftype = EffectType::NoEffect; if (eventFlag & 0x0008) { std::string id = ctr.readString(pcsr, 2); step.setEffectID(0, id); efftype = Effect::toEffectType(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0010) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(0, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0020) { std::string id = ctr.readString(pcsr, 2); step.setEffectID(1, id); efftype = Effect::toEffectType(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0040) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(1, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0080) { std::string id = ctr.readString(pcsr, 2); step.setEffectID(2, id); efftype = Effect::toEffectType(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0100) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(2, v); } efftype = EffectType::NoEffect; if (eventFlag & 0x0200) { std::string id = ctr.readString(pcsr, 2); step.setEffectID(3, id); efftype = Effect::toEffectType(sndsrc, id); pcsr += 2; } if (eventFlag & 0x0400) { int v = ctr.readUint8(pcsr++); if (version < Version::toBCD(1, 3, 1) && efftype == EffectType::NoisePitch && v < 32) v = 31 - v; step.setEffectValue(3, v); } } } scsr += trackOfs; } } return globCsr + songOfs; } BambooTracker-0.3.5/BambooTracker/io/module_io.hpp000066400000000000000000000025701362177441300220550ustar00rootroot00000000000000#pragma once #include #include "module.hpp" #include "instruments_manager.hpp" #include "binary_container.hpp" class ModuleIO { public: static void saveModule(BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan); static void loadModule(BinaryContainer& ctr, std::weak_ptr mod, std::weak_ptr instMan); private: ModuleIO(); static size_t loadModuleSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version); static size_t loadInstrumentSectionInModule(std::weak_ptr instMan, BinaryContainer& ctr, size_t globCsr, uint32_t version); static size_t loadInstrumentPropertySectionInModule(std::weak_ptr instMan, BinaryContainer& ctr, size_t globCsr, uint32_t version); static size_t loadInstrumentPropertyOperatorSequence(FMEnvelopeParameter param, size_t instMemCsr, std::weak_ptr instMan, BinaryContainer& ctr, uint32_t version); static size_t loadGrooveSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version); static size_t loadSongSectionInModule(std::weak_ptr mod, BinaryContainer& ctr, size_t globCsr, uint32_t version); }; BambooTracker-0.3.5/BambooTracker/io/s98_tag.hpp000066400000000000000000000003561362177441300213570ustar00rootroot00000000000000#pragma once #include struct S98Tag { std::string title; std::string artist; std::string game; std::string year; std::string genre; std::string comment; std::string copyright; std::string s98by; std::string system; }; BambooTracker-0.3.5/BambooTracker/jam_manager.cpp000066400000000000000000000127521362177441300217310ustar00rootroot00000000000000#include "jam_manager.hpp" #include #include #include JamManager::JamManager() : isJamMode_(true), isPoly_(true) { clear(); } bool JamManager::toggleJamMode() { isJamMode_ = !isJamMode_; return isJamMode_; } bool JamManager::isJamMode() const { return isJamMode_; } void JamManager::polyphonic(bool flag) { isPoly_ = flag; clear(); } std::vector JamManager::keyOn(JamKey key, int channel, SoundSource source, int keyNum) { std::vector keyDataList; JamKeyData onData{ key, channel, source, keyNum }; std::deque* unusedCh = nullptr; switch (source) { case SoundSource::FM: unusedCh = &unusedChFM_; break; case SoundSource::SSG: unusedCh = &unusedChSSG_; break; default: break; } if (!unusedCh->empty()) { if (key == JamKey::MidiKey) { if (isPoly_) onData.channelInSource = unusedCh->front(); unusedCh->pop_front(); keyOnTable_.push_back(onData); keyDataList.push_back(onData); } else { auto&& it = std::find_if(keyOnTable_.begin(), keyOnTable_.end(), [&](JamKeyData x) { return (x.source == source && x.key == key); }); if (it == keyOnTable_.end()) { if (isPoly_) onData.channelInSource = unusedCh->front(); unusedCh->pop_front(); keyOnTable_.push_back(onData); keyDataList.push_back(onData); } else { JamKeyData del = *it; if (isPoly_) onData.channelInSource = del.channelInSource; keyDataList.push_back(onData); keyDataList.push_back(del); keyOnTable_.erase(it); keyOnTable_.push_back(onData); } } } else { auto&& it = std::find_if(keyOnTable_.begin(), keyOnTable_.end(), [&](JamKeyData x) { return (x.source == source); }); JamKeyData del = *it; if (isPoly_) onData.channelInSource = del.channelInSource; keyDataList.push_back(onData); keyDataList.push_back(del); keyOnTable_.erase(it); keyOnTable_.push_back(onData); } return keyDataList; } JamKeyData JamManager::keyOff(JamKey key, int keyNum) { JamKeyData keyData; auto cond = (key == JamKey::MidiKey) ? std::function([&](JamKeyData x) -> bool { return (x.key == JamKey::MidiKey && x.keyNum == keyNum); }) : std::function([&](JamKeyData x) -> bool { return (x.key == key); }); auto&& it = std::find_if(keyOnTable_.begin(), keyOnTable_.end(), cond); if (it == keyOnTable_.end()) { keyData.channelInSource = -1; // Already released } else { JamKeyData data = *it; keyData.channelInSource = data.channelInSource; keyData.key = key; keyData.source = data.source; switch (keyData.source) { case SoundSource::FM: unusedChFM_.push_front(keyData.channelInSource); break; case SoundSource::SSG: unusedChSSG_.push_front(keyData.channelInSource); break; default: break; } keyOnTable_.erase(it); } return keyData; } Note JamManager::jamKeyToNote(JamKey &key) { switch (key) { case JamKey::LowC: case JamKey::LowC2: case JamKey::HighC: case JamKey::HighC2: return Note::C; case JamKey::LowCS: case JamKey::LowCS2: case JamKey::HighCS: case JamKey::HighCS2: return Note::CS; case JamKey::LowD: case JamKey::LowD2: case JamKey::HighD: case JamKey::HighD2: return Note::D; case JamKey::LowDS: case JamKey::HighDS: return Note::DS; case JamKey::LowE: case JamKey::HighE: return Note::E; case JamKey::LowF: case JamKey::HighF: return Note::F; case JamKey::LowFS: case JamKey::HighFS: return Note::FS; case JamKey::LowG: case JamKey::HighG: return Note::G; case JamKey::LowGS: case JamKey::HighGS: return Note::GS; case JamKey::LowA: case JamKey::HighA: return Note::A; case JamKey::LowAS: case JamKey::HighAS: return Note::AS; case JamKey::LowB: case JamKey::HighB: return Note::B; default: throw std::invalid_argument("Unexpected JamKey."); } } JamKey JamManager::noteToJamKey(Note& note) { switch (note) { case Note::C: return JamKey::LowC; case Note::CS: return JamKey::LowCS; case Note::D: return JamKey::LowD; case Note::DS: return JamKey::LowDS; case Note::E: return JamKey::LowE; case Note::F: return JamKey::LowF; case Note::FS: return JamKey::LowFS; case Note::G: return JamKey::LowG; case Note::GS: return JamKey::LowGS; case Note::A: return JamKey::LowA; case Note::AS: return JamKey::LowAS; case Note::B: return JamKey::LowB; default: throw std::invalid_argument("Unexpected Note."); } } int JamManager::calcOctave(int baseOctave, JamKey &key) { switch (key) { case JamKey::LowC: case JamKey::LowCS: case JamKey::LowD: case JamKey::LowDS: case JamKey::LowE: case JamKey::LowF: case JamKey::LowFS: case JamKey::LowG: case JamKey::LowGS: case JamKey::LowA: case JamKey::LowAS: case JamKey::LowB: return baseOctave; case JamKey::LowC2: case JamKey::LowCS2: case JamKey::LowD2: case JamKey::HighC: case JamKey::HighCS: case JamKey::HighD: case JamKey::HighDS: case JamKey::HighE: case JamKey::HighF: case JamKey::HighFS: case JamKey::HighG: case JamKey::HighGS: case JamKey::HighA: case JamKey::HighAS: case JamKey::HighB: return (baseOctave + 1); case JamKey::HighC2: case JamKey::HighCS2: case JamKey::HighD2: return (baseOctave + 2); default: throw std::invalid_argument("Unexpected JamKey."); } } void JamManager::clear() { if (isPoly_) { unusedChFM_ = std::deque(6); unusedChSSG_= std::deque(3); auto gennums = [i = 0]() mutable -> int { return i++; }; std::generate(unusedChFM_.begin(), unusedChFM_.end(), gennums); std::generate(unusedChSSG_.begin(), unusedChSSG_.end(), gennums); } else { unusedChFM_.resize(1); unusedChSSG_.resize(1); } } BambooTracker-0.3.5/BambooTracker/jam_manager.hpp000066400000000000000000000017321362177441300217320ustar00rootroot00000000000000#pragma once #include #include #include "misc.hpp" struct JamKeyData; enum class JamKey; class JamManager { public: JamManager(); bool toggleJamMode(); bool isJamMode() const; void polyphonic(bool flag); std::vector keyOn(JamKey key, int channel, SoundSource source, int keyNum); JamKeyData keyOff(JamKey key, int keyNum); static Note jamKeyToNote(JamKey& key); static JamKey noteToJamKey(Note& note); static int calcOctave(int baseOctave, JamKey& key); void clear(); private: bool isJamMode_; bool isPoly_; std::vector keyOnTable_; std::deque unusedChFM_, unusedChSSG_; }; struct JamKeyData { JamKey key; int channelInSource; SoundSource source; int keyNum; }; enum class JamKey { LowC, LowCS, LowD, LowDS, LowE, LowF, LowFS, LowG, LowGS, LowA, LowAS, LowB, LowC2, LowCS2, LowD2, HighC, HighCS, HighD, HighDS, HighE, HighF, HighFS, HighG, HighGS, HighA, HighAS, HighB, HighC2, HighCS2, HighD2, MidiKey }; BambooTracker-0.3.5/BambooTracker/main.cpp000066400000000000000000000071211362177441300204060ustar00rootroot00000000000000#include "./gui/mainwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include "configuration.hpp" #include "gui/q_application_wrapper.hpp" #include "gui/configuration_handler.hpp" // Localization static void setupTranslations(); static QString findQtTranslationsDir(); static QString findAppTranslationsDir(); int main(int argc, char *argv[]) { try { QString filePath = (argc > 1) ? argv[argc - 1] : ""; // Last argument file std::shared_ptr config = std::make_shared(); ConfigurationHandler::loadConfiguration(config); #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif std::unique_ptr a(std::make_unique(argc, argv)); if (config->getEnableTranslation()) setupTranslations(); a->setWindowIcon(QIcon(":/icon/app_icon")); std::unique_ptr w(std::make_unique(config, filePath)); w->show(); int ret = a->exec(); ConfigurationHandler::saveConfiguration(config); if (ret) QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occurred.")); return ret; } catch (std::exception& e) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("An unknown error occurred.\n%1").arg(e.what())); return 1; } } // Sets up the translation according to the current language static void setupTranslations() { QApplication *a = qApp; const QString lang = QLocale::system().name(); QTranslator *qtTr = new QTranslator(a); QTranslator *appTr = new QTranslator(a); QString qtDir = findQtTranslationsDir(); QString appDir = findAppTranslationsDir(); if (!qtDir.isEmpty()) { QString baseName = "qt_" + lang; qtTr->load(baseName, qtDir); qDebug() << "Translation" << baseName << "from" << qtDir; } if (!appDir.isEmpty()) { QString baseName = "bamboo_tracker_" + lang; appTr->load(baseName, appDir); qDebug() << "Translation" << baseName << "from" << appDir; } a->installTranslator(qtTr); a->installTranslator(appTr); } // Finds the location of Qt translation catalogs static QString findQtTranslationsDir() { #if defined(Q_OS_DARWIN) // if this is macOS, attempt to load from inside an app bundle QString pathInAppBundle = QApplication::applicationDirPath() + "/../Resources/lang"; if (QDir(pathInAppBundle).exists()) return pathInAppBundle; #endif #if defined(Q_OS_WIN) // if this is Windows, translations should be distributed with the program return QApplication::applicationDirPath() + "/lang"; #else // the files are located in the installation of Qt return QLibraryInfo::location(QLibraryInfo::TranslationsPath); #endif } // Finds the location of our translation catalogs static QString findAppTranslationsDir() { #ifndef QT_NO_DEBUG // if this is a debug build, attempt to load from the source directory QString pathInSources = QApplication::applicationDirPath() + "/.qm"; if (QDir(pathInSources).exists()) return pathInSources; #endif #if defined(Q_OS_DARWIN) // if this is macOS, attempt to load from inside an app bundle QString pathInAppBundle = QApplication::applicationDirPath() + "/../Resources/lang"; if (QDir(pathInAppBundle).exists()) return pathInAppBundle; #endif #if defined(Q_OS_WIN) // if this is Windows, translations should be distributed with the program return QApplication::applicationDirPath() + "/lang"; #else return QApplication::applicationDirPath() + "/../share/BambooTracker/lang"; #endif } BambooTracker-0.3.5/BambooTracker/midi/000077500000000000000000000000001362177441300176775ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/midi/RtMidi/000077500000000000000000000000001362177441300210675ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/midi/RtMidi/RtMidi.cpp000066400000000000000000003241471362177441300227760ustar00rootroot00000000000000/**********************************************************************/ /*! \class RtMidi \brief An abstract base class for realtime MIDI input/output. This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2017 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**********************************************************************/ #include "RtMidi.h" #include #if defined(__MACOSX_CORE__) #if TARGET_OS_IPHONE #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos #endif #endif // Default for Windows is to add an identifier to the port names; this // flag can be defined (e.g. in your project file) to disable this behaviour. //#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES // **************************************************************** // // // MidiInApi and MidiOutApi subclass prototypes. // // **************************************************************** // #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) #define __RTMIDI_DUMMY__ #endif #if defined(__MACOSX_CORE__) class MidiInCore: public MidiInApi { public: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); ~MidiInCore( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutCore: public MidiOutApi { public: MidiOutCore( const std::string &clientName ); ~MidiOutCore( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( const unsigned char *message, size_t size ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__UNIX_JACK__) class MidiInJack: public MidiInApi { public: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); ~MidiInJack( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: std::string clientName; void connect( void ); void initialize( const std::string& clientName ); }; class MidiOutJack: public MidiOutApi { public: MidiOutJack( const std::string &clientName ); ~MidiOutJack( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( const unsigned char *message, size_t size ); protected: std::string clientName; void connect( void ); void initialize( const std::string& clientName ); }; #endif #if defined(__LINUX_ALSA__) class MidiInAlsa: public MidiInApi { public: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); ~MidiInAlsa( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutAlsa: public MidiOutApi { public: MidiOutAlsa( const std::string &clientName ); ~MidiOutAlsa( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( const unsigned char *message, size_t size ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__WINDOWS_MM__) class MidiInWinMM: public MidiInApi { public: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); ~MidiInWinMM( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutWinMM: public MidiOutApi { public: MidiOutWinMM( const std::string &clientName ); ~MidiOutWinMM( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; void openPort( unsigned int portNumber, const std::string &portName ); void openVirtualPort( const std::string &portName ); void closePort( void ); void setClientName( const std::string &clientName ); void setPortName( const std::string &portName ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( const unsigned char *message, size_t size ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__RTMIDI_DUMMY__) class MidiInDummy: public MidiInApi { public: MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} void openVirtualPort( const std::string &/*portName*/ ) {} void closePort( void ) {} void setClientName( const std::string &/*clientName*/ ) {}; void setPortName( const std::string &/*portName*/ ) {}; unsigned int getPortCount( void ) { return 0; } std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } protected: void initialize( const std::string& /*clientName*/ ) {} }; class MidiOutDummy: public MidiOutApi { public: MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} void openVirtualPort( const std::string &/*portName*/ ) {} void closePort( void ) {} void setClientName( const std::string &/*clientName*/ ) {}; void setPortName( const std::string &/*portName*/ ) {}; unsigned int getPortCount( void ) { return 0; } std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} protected: void initialize( const std::string& /*clientName*/ ) {} }; #endif //*********************************************************************// // RtMidi Definitions //*********************************************************************// RtMidi :: RtMidi() : rtapi_(0) { } RtMidi :: ~RtMidi() { delete rtapi_; rtapi_ = 0; } std::string RtMidi :: getVersion( void ) throw() { return std::string( RTMIDI_VERSION ); } void RtMidi :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); // The order here will control the order of RtMidi's API search in // the constructor. #if defined(__MACOSX_CORE__) apis.push_back( MACOSX_CORE ); #endif #if defined(__LINUX_ALSA__) apis.push_back( LINUX_ALSA ); #endif #if defined(__UNIX_JACK__) apis.push_back( UNIX_JACK ); #endif #if defined(__WINDOWS_MM__) apis.push_back( WINDOWS_MM ); #endif #if defined(__RTMIDI_DUMMY__) apis.push_back( RTMIDI_DUMMY ); #endif } void RtMidi :: setClientName( const std::string &clientName ) { rtapi_->setClientName(clientName); } void RtMidi :: setPortName( const std::string &portName ) { rtapi_->setPortName(portName); } //*********************************************************************// // RtMidiIn Definitions //*********************************************************************// void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) { delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new MidiInJack( clientName, queueSizeLimit ); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); #endif #if defined(__WINDOWS_MM__) if ( api == WINDOWS_MM ) rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiInCore( clientName, queueSizeLimit ); #endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); #endif } RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) : RtMidi() { if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName, queueSizeLimit ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a warning // and continue as if no API was specified. std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one port or we reach the end of the list. std::vector< RtMidi::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetPortCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll throw an error. std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiIn :: ~RtMidiIn() throw() { } //*********************************************************************// // RtMidiOut Definitions //*********************************************************************// void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) { delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new MidiOutJack( clientName ); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new MidiOutAlsa( clientName ); #endif #if defined(__WINDOWS_MM__) if ( api == WINDOWS_MM ) rtapi_ = new MidiOutWinMM( clientName ); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiOutCore( clientName ); #endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiOutDummy( clientName ); #endif } RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) { if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a warning // and continue as if no API was specified. std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one port or we reach the end of the list. std::vector< RtMidi::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetPortCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll thrown an error. std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiOut :: ~RtMidiOut() throw() { } //*********************************************************************// // Common MidiApi Definitions //*********************************************************************// MidiApi :: MidiApi( void ) : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0) { } MidiApi :: ~MidiApi( void ) { } void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) { errorCallback_ = errorCallback; errorCallbackUserData_ = userData; } void MidiApi :: error( RtMidiError::Type type, std::string errorString ) { if ( errorCallback_ ) { if ( firstErrorOccurred_ ) return; firstErrorOccurred_ = true; const std::string errorMessage = errorString; errorCallback_( type, errorMessage, errorCallbackUserData_); firstErrorOccurred_ = false; return; } if ( type == RtMidiError::WARNING ) { std::cerr << '\n' << errorString << "\n\n"; } else if ( type == RtMidiError::DEBUG_WARNING ) { #if defined(__RTMIDI_DEBUG__) std::cerr << '\n' << errorString << "\n\n"; #endif } else { std::cerr << '\n' << errorString << "\n\n"; throw RtMidiError( errorString, type ); } } //*********************************************************************// // Common MidiInApi Definitions //*********************************************************************// MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) : MidiApi() { // Allocate the MIDI queue. inputData_.queue.ringSize = queueSizeLimit; if ( inputData_.queue.ringSize > 0 ) inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; } MidiInApi :: ~MidiInApi( void ) { // Delete the MIDI queue. if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; } void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) { if ( inputData_.usingCallback ) { errorString_ = "MidiInApi::setCallback: a callback function is already set!"; error( RtMidiError::WARNING, errorString_ ); return; } if ( !callback ) { errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; error( RtMidiError::WARNING, errorString_ ); return; } inputData_.userCallback = callback; inputData_.userData = userData; inputData_.usingCallback = true; } void MidiInApi :: cancelCallback() { if ( !inputData_.usingCallback ) { errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; error( RtMidiError::WARNING, errorString_ ); return; } inputData_.userCallback = 0; inputData_.userData = 0; inputData_.usingCallback = false; } void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { inputData_.ignoreFlags = 0; if ( midiSysex ) inputData_.ignoreFlags = 0x01; if ( midiTime ) inputData_.ignoreFlags |= 0x02; if ( midiSense ) inputData_.ignoreFlags |= 0x04; } double MidiInApi :: getMessage( std::vector *message ) { message->clear(); if ( inputData_.usingCallback ) { errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; error( RtMidiError::WARNING, errorString_ ); return 0.0; } double timeStamp; if (!inputData_.queue.pop(message, &timeStamp)) return 0.0; return timeStamp; } unsigned int MidiInApi::MidiQueue::size(unsigned int *__back, unsigned int *__front) { // Access back/front members exactly once and make stack copies for // size calculation unsigned int _back = back, _front = front, _size; if (_back >= _front) _size = _back - _front; else _size = ringSize - _front + _back; // Return copies of back/front so no new and unsynchronized accesses // to member variables are needed. if (__back) *__back = _back; if (__front) *__front = _front; return _size; } // As long as we haven't reached our queue size limit, push the message. bool MidiInApi::MidiQueue::push(const MidiInApi::MidiMessage& msg) { // Local stack copies of front/back unsigned int _back, _front, _size; // Get back/front indexes exactly once and calculate current size _size = size(&_back, &_front); if ( _size < ringSize-1 ) { ring[_back] = msg; back = (back+1)%ringSize; return true; } return false; } bool MidiInApi::MidiQueue::pop(std::vector *msg, double* timeStamp) { // Local stack copies of front/back unsigned int _back, _front, _size; // Get back/front indexes exactly once and calculate current size _size = size(&_back, &_front); if (_size == 0) return false; // Copy queued message to the vector pointer argument and then "pop" it. msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() ); *timeStamp = ring[_front].timeStamp; // Update front front = (front+1)%ringSize; return true; } //*********************************************************************// // Common MidiOutApi Definitions //*********************************************************************// MidiOutApi :: MidiOutApi( void ) : MidiApi() { } MidiOutApi :: ~MidiOutApi( void ) { } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) // The CoreMIDI API is based on the use of a callback function for // MIDI input. We convert the system specific time stamps to delta // time values. // OS-X CoreMIDI header files. #include #include #include // A structure to hold variables related to the CoreMIDI API // implementation. struct CoreMidiData { MIDIClientRef client; MIDIPortRef port; MIDIEndpointRef endpoint; MIDIEndpointRef destinationId; unsigned long long lastTime; MIDISysexSendRequest sysexreq; }; //*********************************************************************// // API: OS-X // Class Definitions: MidiInCore //*********************************************************************// static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) { MidiInApi::RtMidiInData *data = static_cast (procRef); CoreMidiData *apiData = static_cast (data->apiData); unsigned char status; unsigned short nBytes, iByte, size; unsigned long long time; bool& continueSysex = data->continueSysex; MidiInApi::MidiMessage& message = data->message; const MIDIPacket *packet = &list->packet[0]; for ( unsigned int i=0; inumPackets; ++i ) { // My interpretation of the CoreMIDI documentation: all message // types, except sysex, are complete within a packet and there may // be several of them in a single packet. Sysex messages can be // broken across multiple packets and PacketLists but are bundled // alone within each packet (these packets do not contain other // message types). If sysex messages are split across multiple // MIDIPacketLists, they must be handled by multiple calls to this // function. nBytes = packet->length; if ( nBytes == 0 ) { packet = MIDIPacketNext(packet); continue; } // Calculate time stamp. if ( data->firstMessage ) { message.timeStamp = 0.0; data->firstMessage = false; } else { time = packet->timeStamp; if ( time == 0 ) { // this happens when receiving asynchronous sysex messages time = AudioGetCurrentHostTime(); } time -= apiData->lastTime; time = AudioConvertHostTimeToNanos( time ); if ( !continueSysex ) message.timeStamp = time * 0.000000001; } // Track whether any non-filtered messages were found in this // packet for timestamp calculation bool foundNonFiltered = false; iByte = 0; if ( continueSysex ) { // We have a continuing, segmented sysex message. if ( !( data->ignoreFlags & 0x01 ) ) { // If we're not ignoring sysex messages, copy the entire packet. for ( unsigned int j=0; jdata[j] ); } continueSysex = packet->data[nBytes-1] != 0xF7; if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if (!data->queue.push(message)) std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); } } else { while ( iByte < nBytes ) { size = 0; // We are expecting that the next byte in the packet is a status byte. status = packet->data[iByte]; if ( !(status & 0x80) ) break; // Determine the number of bytes in the MIDI message. if ( status < 0xC0 ) size = 3; else if ( status < 0xE0 ) size = 2; else if ( status < 0xF0 ) size = 3; else if ( status == 0xF0 ) { // A MIDI sysex if ( data->ignoreFlags & 0x01 ) { size = 0; iByte = nBytes; } else size = nBytes - iByte; continueSysex = packet->data[nBytes-1] != 0xF7; } else if ( status == 0xF1 ) { // A MIDI time code message if ( data->ignoreFlags & 0x02 ) { size = 0; iByte += 2; } else size = 2; } else if ( status == 0xF2 ) size = 3; else if ( status == 0xF3 ) size = 2; else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { // A MIDI timing tick message and we're ignoring it. size = 0; iByte += 1; } else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { // A MIDI active sensing message and we're ignoring it. size = 0; iByte += 1; } else size = 1; // Copy the MIDI data to our vector. if ( size ) { foundNonFiltered = true; message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); if ( !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if (!data->queue.push(message)) std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); } iByte += size; } } } // Save the time of the last non-filtered message if (foundNonFiltered) { apiData->lastTime = packet->timeStamp; if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages apiData->lastTime = AudioGetCurrentHostTime(); } } packet = MIDIPacketNext(packet); } } MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { MidiInCore::initialize( clientName ); } MidiInCore :: ~MidiInCore( void ) { // Close a connection if it exists. MidiInCore::closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } void MidiInCore :: initialize( const std::string& clientName ) { // Set up our client. MIDIClientRef client; CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); if ( result != noErr ) { std::ostringstream ost; ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; errorString_ = ost.str(); error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data; inputData_.apiData = (void *) data; CFRelease(name); } void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) { if ( connected_ ) { errorString_ = "MidiInCore::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nSrc = MIDIGetNumberOfSources(); if (nSrc < 1) { errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nSrc ) { std::ostringstream ost; ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIInputPortCreate( data->client, portNameRef, midiInputCallback, (void *)&inputData_, &port ); CFRelease( portNameRef ); if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Get the desired input source identifier. MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); if ( endpoint == 0 ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Make the connection. result = MIDIPortConnectSource( port, endpoint, NULL ); if ( result != noErr ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific port information. data->port = port; connected_ = true; } void MidiInCore :: openVirtualPort( const std::string &portName ) { CoreMidiData *data = static_cast (apiData_); // Create a virtual MIDI input destination. MIDIEndpointRef endpoint; CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIDestinationCreate( data->client, portNameRef, midiInputCallback, (void *)&inputData_, &endpoint ); CFRelease( portNameRef ); if ( result != noErr ) { errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->endpoint = endpoint; } void MidiInCore :: closePort( void ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { MIDIEndpointDispose( data->endpoint ); data->endpoint = 0; } if ( data->port ) { MIDIPortDispose( data->port ); data->port = 0; } connected_ = false; } void MidiInCore :: setClientName ( const std::string& ) { errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiInCore :: setPortName ( const std::string& ) { errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; error( RtMidiError::WARNING, errorString_ ); } unsigned int MidiInCore :: getPortCount() { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfSources(); } // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; // Begin with the endpoint's name. str = NULL; MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); CFRelease( str ); } MIDIEntityRef entity = 0; MIDIEndpointGetEntity( endpoint, &entity ); if ( entity == 0 ) // probably virtual return result; if ( CFStringGetLength( result ) == 0 ) { // endpoint name has zero length -- try the entity str = NULL; MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); CFRelease( str ); } } // now consider the device's name MIDIDeviceRef device = 0; MIDIEntityGetDevice( entity, &device ); if ( device == 0 ) return result; str = NULL; MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); if ( CFStringGetLength( result ) == 0 ) { CFRelease( result ); return str; } if ( str != NULL ) { // if an external device has only one entity, throw away // the endpoint name and just use the device name if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { CFRelease( result ); return str; } else { if ( CFStringGetLength( str ) == 0 ) { CFRelease( str ); return result; } // does the entity name already start with the device name? // (some drivers do this though they shouldn't) // if so, do not prepend if ( CFStringCompareWithOptions( result, /* endpoint name */ str /* device name */, CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { // prepend the device name to the entity name if ( CFStringGetLength( result ) > 0 ) CFStringInsert( result, 0, CFSTR(" ") ); CFStringInsert( result, 0, str ); } CFRelease( str ); } } return result; } // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; OSStatus err; int i; // Does the endpoint have connections? CFDataRef connections = NULL; int nConnected = 0; bool anyStrings = false; err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); if ( connections != NULL ) { // It has connections, follow them // Concatenate the names of all connected devices nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); if ( nConnected ) { const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); for ( i=0; i= MIDIGetNumberOfSources() ) { std::ostringstream ost; ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } portRef = MIDIGetSource( portNumber ); nameRef = ConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8); CFRelease( nameRef ); return stringName = name; } //*********************************************************************// // API: OS-X // Class Definitions: MidiOutCore //*********************************************************************// MidiOutCore :: MidiOutCore( const std::string &clientName ) : MidiOutApi() { MidiOutCore::initialize( clientName ); } MidiOutCore :: ~MidiOutCore( void ) { // Close a connection if it exists. MidiOutCore::closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } void MidiOutCore :: initialize( const std::string& clientName ) { // Set up our client. MIDIClientRef client; CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); if ( result != noErr ) { std::ostringstream ost; ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; errorString_ = ost.str(); error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data; CFRelease( name ); } unsigned int MidiOutCore :: getPortCount() { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfDestinations(); } std::string MidiOutCore :: getPortName( unsigned int portNumber ) { CFStringRef nameRef; MIDIEndpointRef portRef; char name[128]; std::string stringName; CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); if ( portNumber >= MIDIGetNumberOfDestinations() ) { std::ostringstream ost; ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } portRef = MIDIGetDestination( portNumber ); nameRef = ConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); CFRelease( nameRef ); return stringName = name; } void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName ) { if ( connected_ ) { errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nDest = MIDIGetNumberOfDestinations(); if (nDest < 1) { errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDest ) { std::ostringstream ost; ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); CFRelease( portNameRef ); if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Get the desired output port identifier. MIDIEndpointRef destination = MIDIGetDestination( portNumber ); if ( destination == 0 ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->port = port; data->destinationId = destination; connected_ = true; } void MidiOutCore :: closePort( void ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { MIDIEndpointDispose( data->endpoint ); data->endpoint = 0; } if ( data->port ) { MIDIPortDispose( data->port ); data->port = 0; } connected_ = false; } void MidiOutCore :: setClientName ( const std::string& ) { errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutCore :: setPortName ( const std::string& ) { errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutCore :: openVirtualPort( const std::string &portName ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } // Create a virtual MIDI output source. MIDIEndpointRef endpoint; CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint ); CFRelease( portNameRef ); if ( result != noErr ) { errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->endpoint = endpoint; } void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) { // We use the MIDISendSysex() function to asynchronously send sysex // messages. Otherwise, we use a single CoreMidi MIDIPacket. unsigned int nBytes = static_cast (size); if ( nBytes == 0 ) { errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; error( RtMidiError::WARNING, errorString_ ); return; } MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); CoreMidiData *data = static_cast (apiData_); OSStatus result; if ( message[0] != 0xF0 && nBytes > 3 ) { errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; error( RtMidiError::WARNING, errorString_ ); return; } Byte buffer[nBytes+(sizeof(MIDIPacketList))]; ByteCount listSize = sizeof(buffer); MIDIPacketList *packetList = (MIDIPacketList*)buffer; MIDIPacket *packet = MIDIPacketListInit( packetList ); ByteCount remainingBytes = nBytes; while (remainingBytes && packet) { ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr); remainingBytes -= bytesForPacket; } if ( !packet ) { errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Send to any destinations that may have connected to us. if ( data->endpoint ) { result = MIDIReceived( data->endpoint, packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; error( RtMidiError::WARNING, errorString_ ); } } // And send to an explicit destination port if we're connected. if ( connected_ ) { result = MIDISend( data->port, data->destinationId, packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; error( RtMidiError::WARNING, errorString_ ); } } } #endif // __MACOSX_CORE__ //*********************************************************************// // API: LINUX ALSA SEQUENCER //*********************************************************************// // API information found at: // - http://www.alsa-project.org/documentation.php#Library #if defined(__LINUX_ALSA__) // The ALSA Sequencer API is based on the use of a callback function for // MIDI input. // // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer // time stamps and other assorted fixes!!! // If you don't need timestamping for incoming MIDI events, define the // preprocessor definition AVOID_TIMESTAMPING to save resources // associated with the ALSA sequencer queues. #include #include // ALSA header file. #include // A structure to hold variables related to the ALSA API // implementation. struct AlsaMidiData { snd_seq_t *seq; unsigned int portNum; int vport; snd_seq_port_subscribe_t *subscription; snd_midi_event_t *coder; unsigned int bufferSize; unsigned char *buffer; pthread_t thread; pthread_t dummy_thread_id; snd_seq_real_time_t lastTime; int queue_id; // an input queue is needed to get timestamped events int trigger_fds[2]; }; #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiInAlsa //*********************************************************************// static void *alsaMidiHandler( void *ptr ) { MidiInApi::RtMidiInData *data = static_cast (ptr); AlsaMidiData *apiData = static_cast (data->apiData); long nBytes; double time; bool continueSysex = false; bool doDecode = false; MidiInApi::MidiMessage message; int poll_fd_count; struct pollfd *poll_fds; snd_seq_event_t *ev; int result; apiData->bufferSize = 32; result = snd_midi_event_new( 0, &apiData->coder ); if ( result < 0 ) { data->doInput = false; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; return 0; } unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); if ( buffer == NULL ) { data->doInput = false; snd_midi_event_free( apiData->coder ); apiData->coder = 0; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; return 0; } snd_midi_event_init( apiData->coder ); snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); poll_fds[0].fd = apiData->trigger_fds[0]; poll_fds[0].events = POLLIN; while ( data->doInput ) { if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { // No data pending if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { if ( poll_fds[0].revents & POLLIN ) { bool dummy; int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); (void) res; } } continue; } // If here, there should be data. result = snd_seq_event_input( apiData->seq, &ev ); if ( result == -ENOSPC ) { std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; continue; } else if ( result <= 0 ) { std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; perror("System reports"); continue; } // This is a bit weird, but we now have to decode an ALSA MIDI // event (back) into MIDI bytes. We'll ignore non-MIDI types. if ( !continueSysex ) message.bytes.clear(); doDecode = false; switch ( ev->type ) { case SND_SEQ_EVENT_PORT_SUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; #endif break; case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" << (int) ev->data.connect.sender.port << ", dest = " << (int) ev->data.connect.dest.client << ":" << (int) ev->data.connect.dest.port << std::endl; #endif break; case SND_SEQ_EVENT_QFRAME: // MIDI time code if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_SENSING: // Active sensing if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; break; case SND_SEQ_EVENT_SYSEX: if ( (data->ignoreFlags & 0x01) ) break; if ( ev->data.ext.len > apiData->bufferSize ) { apiData->bufferSize = ev->data.ext.len; free( buffer ); buffer = (unsigned char *) malloc( apiData->bufferSize ); if ( buffer == NULL ) { data->doInput = false; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; break; } } doDecode = true; break; default: doDecode = true; } if ( doDecode ) { nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); if ( nBytes > 0 ) { // The ALSA sequencer has a maximum buffer size for MIDI sysex // events of 256 bytes. If a device sends sysex messages larger // than this, they are segmented into 256 byte chunks. So, // we'll watch for this and concatenate sysex chunks into a // single sysex message if necessary. if ( !continueSysex ) message.bytes.assign( buffer, &buffer[nBytes] ); else message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); if ( !continueSysex ) { // Calculate the time stamp: message.timeStamp = 0.0; // Method 1: Use the system time. //(void)gettimeofday(&tv, (struct timezone *)NULL); //time = (tv.tv_sec * 1000000) + tv.tv_usec; // Method 2: Use the ALSA sequencer event time data. // (thanks to Pedro Lopez-Cabanillas!). // Using method from: // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html // Perform the carry for the later subtraction by updating y. // Temp var y is timespec because computation requires signed types, // while snd_seq_real_time_t has unsigned types. snd_seq_real_time_t &x(ev->time.time); struct timespec y; y.tv_nsec = apiData->lastTime.tv_nsec; y.tv_sec = apiData->lastTime.tv_sec; if (x.tv_nsec < y.tv_nsec) { int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; y.tv_nsec -= 1000000000 * nsec; y.tv_sec += nsec; } if (x.tv_nsec - y.tv_nsec > 1000000000) { int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; y.tv_nsec += 1000000000 * nsec; y.tv_sec -= nsec; } // Compute the time difference. time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9; apiData->lastTime = ev->time.time; if ( data->firstMessage == true ) data->firstMessage = false; else message.timeStamp = time; } else { #if defined(__RTMIDI_DEBUG__) std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; #endif } } } snd_seq_free_event( ev ); if ( message.bytes.size() == 0 || continueSysex ) continue; if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if (!data->queue.push(message)) std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; } } if ( buffer ) free( buffer ); snd_midi_event_free( apiData->coder ); apiData->coder = 0; apiData->thread = apiData->dummy_thread_id; return 0; } MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { MidiInAlsa::initialize( clientName ); } MidiInAlsa :: ~MidiInAlsa() { // Close a connection if it exists. MidiInAlsa::closePort(); // Shutdown the input thread. AlsaMidiData *data = static_cast (apiData_); if ( inputData_.doInput ) { inputData_.doInput = false; int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); (void) res; if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); } // Cleanup. close ( data->trigger_fds[0] ); close ( data->trigger_fds[1] ); if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); #ifndef AVOID_TIMESTAMPING snd_seq_free_queue( data->seq, data->queue_id ); #endif snd_seq_close( data->seq ); delete data; } void MidiInAlsa :: initialize( const std::string& clientName ) { // Set up the ALSA sequencer client. snd_seq_t *seq; int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); if ( result < 0 ) { errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; data->portNum = -1; data->vport = -1; data->subscription = 0; data->dummy_thread_id = pthread_self(); data->thread = data->dummy_thread_id; data->trigger_fds[0] = -1; data->trigger_fds[1] = -1; apiData_ = (void *) data; inputData_.apiData = (void *) data; if ( pipe(data->trigger_fds) == -1 ) { errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Create the input queue #ifndef AVOID_TIMESTAMPING data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); // Set arbitrary tempo (mm=100) and resolution (240) snd_seq_queue_tempo_t *qtempo; snd_seq_queue_tempo_alloca(&qtempo); snd_seq_queue_tempo_set_tempo(qtempo, 600000); snd_seq_queue_tempo_set_ppq(qtempo, 240); snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); snd_seq_drain_output(data->seq); #endif } // This function is used to count or get the pinfo structure for a given port number. unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) { snd_seq_client_info_t *cinfo; int client; int count = 0; snd_seq_client_info_alloca( &cinfo ); snd_seq_client_info_set_client( cinfo, -1 ); while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { client = snd_seq_client_info_get_client( cinfo ); if ( client == 0 ) continue; // Reset query info snd_seq_port_info_set_client( pinfo, client ); snd_seq_port_info_set_port( pinfo, -1 ); while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { unsigned int atyp = snd_seq_port_info_get_type( pinfo ); if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; unsigned int caps = snd_seq_port_info_get_capability( pinfo ); if ( ( caps & type ) != type ) continue; if ( count == portNumber ) return 1; ++count; } } // If a negative portNumber was used, return the port count. if ( portNumber < 0 ) return count; return 0; } unsigned int MidiInAlsa :: getPortCount() { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); } std::string MidiInAlsa :: getPortName( unsigned int portNumber ) { snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca( &cinfo ); snd_seq_port_info_alloca( &pinfo ); std::string stringName; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { int cnum = snd_seq_port_info_get_client( pinfo ); snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name( cinfo ); os << ":"; os << snd_seq_port_info_get_name( pinfo ); os << " "; // These lines added to make sure devices are listed os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port( pinfo ); stringName = os.str(); return stringName; } // If we get here, we didn't find a match. errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; error( RtMidiError::WARNING, errorString_ ); return stringName; } void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName ) { if ( connected_ ) { errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); if ( nSrc < 1 ) { errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } snd_seq_port_info_t *src_pinfo; snd_seq_port_info_alloca( &src_pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { std::ostringstream ost; ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } snd_seq_addr_t sender, receiver; sender.client = snd_seq_port_info_get_client( src_pinfo ); sender.port = snd_seq_port_info_get_port( src_pinfo ); receiver.client = snd_seq_client_id( data->seq ); snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); if ( data->vport < 0 ) { snd_seq_port_info_set_client( pinfo, 0 ); snd_seq_port_info_set_port( pinfo, 0 ); snd_seq_port_info_set_capability( pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_real(pinfo, 1); snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); #endif snd_seq_port_info_set_name(pinfo, portName.c_str() ); data->vport = snd_seq_create_port(data->seq, pinfo); if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->vport = snd_seq_port_info_get_port(pinfo); } receiver.port = data->vport; if ( !data->subscription ) { // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } if ( inputData_.doInput == false ) { // Start the input queue #ifndef AVOID_TIMESTAMPING snd_seq_start_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif // Start our MIDI input thread. pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); inputData_.doInput = true; int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); pthread_attr_destroy(&attr); if ( err ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; error( RtMidiError::THREAD_ERROR, errorString_ ); return; } } connected_ = true; } void MidiInAlsa :: openVirtualPort( const std::string &portName ) { AlsaMidiData *data = static_cast (apiData_); if ( data->vport < 0 ) { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_port_info_set_capability( pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_real(pinfo, 1); snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); #endif snd_seq_port_info_set_name(pinfo, portName.c_str()); data->vport = snd_seq_create_port(data->seq, pinfo); if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->vport = snd_seq_port_info_get_port(pinfo); } if ( inputData_.doInput == false ) { // Wait for old thread to stop, if still running if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); // Start the input queue #ifndef AVOID_TIMESTAMPING snd_seq_start_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif // Start our MIDI input thread. pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); inputData_.doInput = true; int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); pthread_attr_destroy(&attr); if ( err ) { if ( data->subscription ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; } inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; error( RtMidiError::THREAD_ERROR, errorString_ ); return; } } } void MidiInAlsa :: closePort( void ) { AlsaMidiData *data = static_cast (apiData_); if ( connected_ ) { if ( data->subscription ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; } // Stop the input queue #ifndef AVOID_TIMESTAMPING snd_seq_stop_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif connected_ = false; } // Stop thread to avoid triggering the callback, while the port is intended to be closed if ( inputData_.doInput ) { inputData_.doInput = false; int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); (void) res; if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); } } void MidiInAlsa :: setClientName( const std::string &clientName ) { AlsaMidiData *data = static_cast ( apiData_ ); snd_seq_set_client_name( data->seq, clientName.c_str() ); } void MidiInAlsa :: setPortName( const std::string &portName ) { AlsaMidiData *data = static_cast (apiData_); snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_get_port_info( data->seq, data->vport, pinfo ); snd_seq_port_info_set_name( pinfo, portName.c_str() ); snd_seq_set_port_info( data->seq, data->vport, pinfo ); } //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiOutAlsa //*********************************************************************// MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi() { MidiOutAlsa::initialize( clientName ); } MidiOutAlsa :: ~MidiOutAlsa() { // Close a connection if it exists. MidiOutAlsa::closePort(); // Cleanup. AlsaMidiData *data = static_cast (apiData_); if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); if ( data->coder ) snd_midi_event_free( data->coder ); if ( data->buffer ) free( data->buffer ); snd_seq_close( data->seq ); delete data; } void MidiOutAlsa :: initialize( const std::string& clientName ) { // Set up the ALSA sequencer client. snd_seq_t *seq; int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); if ( result1 < 0 ) { errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; data->portNum = -1; data->vport = -1; data->bufferSize = 32; data->coder = 0; data->buffer = 0; int result = snd_midi_event_new( data->bufferSize, &data->coder ); if ( result < 0 ) { delete data; errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { delete data; errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } snd_midi_event_init( data->coder ); apiData_ = (void *) data; } unsigned int MidiOutAlsa :: getPortCount() { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); } std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) { snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca( &cinfo ); snd_seq_port_info_alloca( &pinfo ); std::string stringName; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { int cnum = snd_seq_port_info_get_client(pinfo); snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name(cinfo); os << ":"; os << snd_seq_port_info_get_name( pinfo ); os << " "; // These lines added to make sure devices are listed os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port(pinfo); stringName = os.str(); return stringName; } // If we get here, we didn't find a match. errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; error( RtMidiError::WARNING, errorString_ ); return stringName; } void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName ) { if ( connected_ ) { errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); if (nSrc < 1) { errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { std::ostringstream ost; ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } snd_seq_addr_t sender, receiver; receiver.client = snd_seq_port_info_get_client( pinfo ); receiver.port = snd_seq_port_info_get_port( pinfo ); sender.client = snd_seq_client_id( data->seq ); if ( data->vport < 0 ) { data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } sender.port = data->vport; // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); snd_seq_port_subscribe_set_time_update(data->subscription, 1); snd_seq_port_subscribe_set_time_real(data->subscription, 1); if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiOutAlsa :: closePort( void ) { if ( connected_ ) { AlsaMidiData *data = static_cast (apiData_); snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; connected_ = false; } } void MidiOutAlsa :: setClientName( const std::string &clientName ) { AlsaMidiData *data = static_cast ( apiData_ ); snd_seq_set_client_name( data->seq, clientName.c_str() ); } void MidiOutAlsa :: setPortName( const std::string &portName ) { AlsaMidiData *data = static_cast (apiData_); snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_get_port_info( data->seq, data->vport, pinfo ); snd_seq_port_info_set_name( pinfo, portName.c_str() ); snd_seq_set_port_info( data->seq, data->vport, pinfo ); } void MidiOutAlsa :: openVirtualPort( const std::string &portName ) { AlsaMidiData *data = static_cast (apiData_); if ( data->vport < 0 ) { data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) { int result; AlsaMidiData *data = static_cast (apiData_); unsigned int nBytes = static_cast (size); if ( nBytes > data->bufferSize ) { data->bufferSize = nBytes; result = snd_midi_event_resize_buffer ( data->coder, nBytes); if ( result != 0 ) { errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } free (data->buffer); data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } } snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_source(&ev, data->vport); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); for ( unsigned int i=0; ibuffer[i] = message[i]; result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); if ( result < (int)nBytes ) { errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; error( RtMidiError::WARNING, errorString_ ); return; } // Send the event. result = snd_seq_event_output(data->seq, &ev); if ( result < 0 ) { errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; error( RtMidiError::WARNING, errorString_ ); return; } snd_seq_drain_output(data->seq); } #endif // __LINUX_ALSA__ //*********************************************************************// // API: Windows Multimedia Library (MM) //*********************************************************************// // API information deciphered from: // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp // Thanks to Jean-Baptiste Berruchon for the sysex code. #if defined(__WINDOWS_MM__) // The Windows MM API is based on the use of a callback function for // MIDI input. We convert the system specific time stamps to delta // time values. // Windows MM MIDI header files. #include #include // Convert a null-terminated wide string or ANSI-encoded string to UTF-8. static std::string ConvertToUTF8(const TCHAR *str) { std::string u8str; const WCHAR *wstr = L""; #if defined( UNICODE ) || defined( _UNICODE ) wstr = str; #else // Convert from ANSI encoding to wide string int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); std::wstring wstrtemp; if ( wlength ) { wstrtemp.assign( wlength - 1, 0 ); MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength ); wstr = &wstrtemp[0]; } #endif // Convert from wide string to UTF-8 int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL ); if ( length ) { u8str.assign( length - 1, 0 ); length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL ); } return u8str; } #define RT_SYSEX_BUFFER_SIZE 1024 #define RT_SYSEX_BUFFER_COUNT 4 // A structure to hold variables related to the CoreMIDI API // implementation. struct WinMidiData { HMIDIIN inHandle; // Handle to Midi Input Device HMIDIOUT outHandle; // Handle to Midi Output Device DWORD lastTime; MidiInApi::MidiMessage message; LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo }; //*********************************************************************// // API: Windows MM // Class Definitions: MidiInWinMM //*********************************************************************// static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage, DWORD timestamp ) { if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; //MidiInApi::RtMidiInData *data = static_cast (instancePtr); MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; WinMidiData *apiData = static_cast (data->apiData); // Calculate time stamp. if ( data->firstMessage == true ) { apiData->message.timeStamp = 0.0; data->firstMessage = false; } else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; if ( inputStatus == MIM_DATA ) { // Channel or system message // Make sure the first byte is a status byte. unsigned char status = (unsigned char) (midiMessage & 0x000000FF); if ( !(status & 0x80) ) return; // Determine the number of bytes in the MIDI message. unsigned short nBytes = 1; if ( status < 0xC0 ) nBytes = 3; else if ( status < 0xE0 ) nBytes = 2; else if ( status < 0xF0 ) nBytes = 3; else if ( status == 0xF1 ) { if ( data->ignoreFlags & 0x02 ) return; else nBytes = 2; } else if ( status == 0xF2 ) nBytes = 3; else if ( status == 0xF3 ) nBytes = 2; else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { // A MIDI timing tick message and we're ignoring it. return; } else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { // A MIDI active sensing message and we're ignoring it. return; } // Copy bytes to our MIDI message. unsigned char *ptr = (unsigned char *) &midiMessage; for ( int i=0; imessage.bytes.push_back( *ptr++ ); } else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) MIDIHDR *sysex = ( MIDIHDR *) midiMessage; if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { // Sysex message and we're not ignoring it for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) apiData->message.bytes.push_back( sysex->lpData[i] ); } // The WinMM API requires that the sysex buffer be requeued after // input of each sysex message. Even if we are ignoring sysex // messages, we still need to requeue the buffer in case the user // decides to not ignore sysex messages in the future. However, // it seems that WinMM calls this function with an empty sysex // buffer when an application closes and in this case, we should // avoid requeueing it, else the computer suddenly reboots after // one or two minutes. if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { //if ( sysex->dwBytesRecorded > 0 ) { EnterCriticalSection( &(apiData->_mutex) ); MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); LeaveCriticalSection( &(apiData->_mutex) ); if ( result != MMSYSERR_NOERROR ) std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; if ( data->ignoreFlags & 0x01 ) return; } else return; } // Save the time of the last non-filtered message apiData->lastTime = timestamp; if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if (!data->queue.push(apiData->message)) std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; } // Clear the vector for the next input message. apiData->message.bytes.clear(); } MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { MidiInWinMM::initialize( clientName ); } MidiInWinMM :: ~MidiInWinMM() { // Close a connection if it exists. MidiInWinMM::closePort(); WinMidiData *data = static_cast (apiData_); DeleteCriticalSection( &(data->_mutex) ); // Cleanup. delete data; } void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) { // We'll issue a warning here if no devices are available but not // throw an error since the user can plugin something later. unsigned int nDevices = midiInGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. WinMidiData *data = (WinMidiData *) new WinMidiData; apiData_ = (void *) data; inputData_.apiData = (void *) data; data->message.bytes.clear(); // needs to be empty for first input message if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; error( RtMidiError::WARNING, errorString_ ); } } void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) { if ( connected_ ) { errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiInGetNumDevs(); if (nDevices == 0) { errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } WinMidiData *data = static_cast (apiData_); MMRESULT result = midiInOpen( &data->inHandle, portNumber, (DWORD_PTR)&midiInputCallback, (DWORD_PTR)&inputData_, CALLBACK_FUNCTION ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Allocate and init the sysex buffers. for ( int i=0; isysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator data->sysexBuffer[i]->dwFlags = 0; result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); data->inHandle = 0; errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Register the buffer. result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); data->inHandle = 0; errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } result = midiInStart( data->inHandle ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); data->inHandle = 0; errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiInWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); EnterCriticalSection( &(data->_mutex) ); midiInReset( data->inHandle ); midiInStop( data->inHandle ); for ( int i=0; iinHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); delete [] data->sysexBuffer[i]->lpData; delete [] data->sysexBuffer[i]; if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); data->inHandle = 0; errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } midiInClose( data->inHandle ); data->inHandle = 0; connected_ = false; LeaveCriticalSection( &(data->_mutex) ); } } void MidiInWinMM :: setClientName ( const std::string& ) { errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiInWinMM :: setPortName ( const std::string& ) { errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; error( RtMidiError::WARNING, errorString_ ); } unsigned int MidiInWinMM :: getPortCount() { return midiInGetNumDevs(); } std::string MidiInWinMM :: getPortName( unsigned int portNumber ) { std::string stringName; unsigned int nDevices = midiInGetNumDevs(); if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } MIDIINCAPS deviceCaps; midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); stringName = ConvertToUTF8( deviceCaps.szPname ); // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES std::ostringstream os; os << " "; os << portNumber; stringName += os.str(); #endif return stringName; } //*********************************************************************// // API: Windows MM // Class Definitions: MidiOutWinMM //*********************************************************************// MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi() { MidiOutWinMM::initialize( clientName ); } MidiOutWinMM :: ~MidiOutWinMM() { // Close a connection if it exists. MidiOutWinMM::closePort(); // Cleanup. WinMidiData *data = static_cast (apiData_); delete data; } void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) { // We'll issue a warning here if no devices are available but not // throw an error since the user can plug something in later. unsigned int nDevices = midiOutGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. WinMidiData *data = (WinMidiData *) new WinMidiData; apiData_ = (void *) data; } unsigned int MidiOutWinMM :: getPortCount() { return midiOutGetNumDevs(); } std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) { std::string stringName; unsigned int nDevices = midiOutGetNumDevs(); if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } MIDIOUTCAPS deviceCaps; midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); stringName = ConvertToUTF8( deviceCaps.szPname ); // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name std::ostringstream os; #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES os << " "; os << portNumber; stringName += os.str(); #endif return stringName; } void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) { if ( connected_ ) { errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiOutGetNumDevs(); if (nDevices < 1) { errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } WinMidiData *data = static_cast (apiData_); MMRESULT result = midiOutOpen( &data->outHandle, portNumber, (DWORD)NULL, (DWORD)NULL, CALLBACK_NULL ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiOutWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); midiOutReset( data->outHandle ); midiOutClose( data->outHandle ); data->outHandle = 0; connected_ = false; } } void MidiOutWinMM :: setClientName ( const std::string& ) { errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutWinMM :: setPortName ( const std::string& ) { errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) { if ( !connected_ ) return; unsigned int nBytes = static_cast(size); if ( nBytes == 0 ) { errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; error( RtMidiError::WARNING, errorString_ ); return; } MMRESULT result; WinMidiData *data = static_cast (apiData_); if ( message[0] == 0xF0 ) { // Sysex message // Allocate buffer for sysex data. char *buffer = (char *) malloc( nBytes ); if ( buffer == NULL ) { errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } // Copy data to buffer. for ( unsigned int i=0; ioutHandle, &sysex, sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Send the message. result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Unprepare the buffer and MIDIHDR. while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); free( buffer ); } else { // Channel or system message. // Make sure the message size isn't too big. if ( nBytes > 3 ) { errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; error( RtMidiError::WARNING, errorString_ ); return; } // Pack MIDI bytes into double word. DWORD packet; unsigned char *ptr = (unsigned char *) &packet; for ( unsigned int i=0; ioutHandle, packet ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } #endif // __WINDOWS_MM__ //*********************************************************************// // API: UNIX JACK // // Written primarily by Alexander Svetalkin, with updates for delta // time by Gary Scavone, April 2011. // // *********************************************************************// #if defined(__UNIX_JACK__) // JACK header files #include #include #include #ifdef HAVE_SEMAPHORE #include #endif #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer struct JackMidiData { jack_client_t *client; jack_port_t *port; jack_ringbuffer_t *buffSize; jack_ringbuffer_t *buffMessage; jack_time_t lastTime; #ifdef HAVE_SEMAPHORE sem_t sem_cleanup; sem_t sem_needpost; #endif MidiInApi :: RtMidiInData *rtMidiIn; }; //*********************************************************************// // API: JACK // Class Definitions: MidiInJack //*********************************************************************// static int jackProcessIn( jack_nframes_t nframes, void *arg ) { JackMidiData *jData = (JackMidiData *) arg; MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; jack_midi_event_t event; jack_time_t time; // Is port created? if ( jData->port == NULL ) return 0; void *buff = jack_port_get_buffer( jData->port, nframes ); // We have midi events in buffer int evCount = jack_midi_get_event_count( buff ); for (int j = 0; j < evCount; j++) { MidiInApi::MidiMessage message; message.bytes.clear(); jack_midi_event_get( &event, buff, j ); for ( unsigned int i = 0; i < event.size; i++ ) message.bytes.push_back( event.buffer[i] ); // Compute the delta time. time = jack_get_time(); if ( rtData->firstMessage == true ) rtData->firstMessage = false; else message.timeStamp = ( time - jData->lastTime ) * 0.000001; jData->lastTime = time; if ( !rtData->continueSysex ) { if ( rtData->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; callback( message.timeStamp, &message.bytes, rtData->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if (!rtData->queue.push(message)) std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; } } } return 0; } MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { MidiInJack::initialize( clientName ); } void MidiInJack :: initialize( const std::string& clientName ) { JackMidiData *data = new JackMidiData; apiData_ = (void *) data; data->rtMidiIn = &inputData_; data->port = NULL; data->client = NULL; this->clientName = clientName; connect(); } void MidiInJack :: connect() { JackMidiData *data = static_cast (apiData_); if ( data->client ) return; // Initialize JACK client if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { errorString_ = "MidiInJack::initialize: JACK server not running?"; error( RtMidiError::WARNING, errorString_ ); return; } jack_set_process_callback( data->client, jackProcessIn, data ); jack_activate( data->client ); } MidiInJack :: ~MidiInJack() { JackMidiData *data = static_cast (apiData_); MidiInJack::closePort(); if ( data->client ) jack_client_close( data->client ); delete data; } void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName ) { JackMidiData *data = static_cast (apiData_); connect(); // Creating new port if ( data->port == NULL) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL) { errorString_ = "MidiInJack::openPort: JACK error creating port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); connected_ = true; } void MidiInJack :: openVirtualPort( const std::string &portName ) { JackMidiData *data = static_cast (apiData_); connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } unsigned int MidiInJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); connect(); if ( !data->client ) return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); if ( ports == NULL ) return 0; while ( ports[count] != NULL ) count++; free( ports ); return count; } std::string MidiInJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); std::string retStr(""); connect(); // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); // Check port validity if ( ports == NULL ) { errorString_ = "MidiInJack::getPortName: no ports available!"; error( RtMidiError::WARNING, errorString_ ); return retStr; } unsigned int i; for (i=0; i (apiData_); if ( data->port == NULL ) return; jack_port_unregister( data->client, data->port ); data->port = NULL; connected_ = false; } void MidiInJack:: setClientName( const std::string& ) { errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiInJack :: setPortName( const std::string &portName ) { JackMidiData *data = static_cast (apiData_); #ifdef JACK_HAS_PORT_RENAME jack_port_rename( data->client, data->port, portName.c_str() ); #else jack_port_set_name( data->port, portName.c_str() ); #endif } //*********************************************************************// // API: JACK // Class Definitions: MidiOutJack //*********************************************************************// // Jack process callback static int jackProcessOut( jack_nframes_t nframes, void *arg ) { JackMidiData *data = (JackMidiData *) arg; jack_midi_data_t *midiData; int space; // Is port created? if ( data->port == NULL ) return 0; void *buff = jack_port_get_buffer( data->port, nframes ); jack_midi_clear_buffer( buff ); while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) ); midiData = jack_midi_event_reserve( buff, 0, space ); jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); } #ifdef HAVE_SEMAPHORE if (!sem_trywait(&data->sem_needpost)) sem_post(&data->sem_cleanup); #endif return 0; } MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi() { MidiOutJack::initialize( clientName ); } void MidiOutJack :: initialize( const std::string& clientName ) { JackMidiData *data = new JackMidiData; apiData_ = (void *) data; data->port = NULL; data->client = NULL; #ifdef HAVE_SEMAPHORE sem_init(&data->sem_cleanup, 0, 0); sem_init(&data->sem_needpost, 0, 0); #endif this->clientName = clientName; connect(); } void MidiOutJack :: connect() { JackMidiData *data = static_cast (apiData_); if ( data->client ) return; // Initialize output ringbuffers data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); // Initialize JACK client if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { errorString_ = "MidiOutJack::initialize: JACK server not running?"; error( RtMidiError::WARNING, errorString_ ); return; } jack_set_process_callback( data->client, jackProcessOut, data ); jack_activate( data->client ); } MidiOutJack :: ~MidiOutJack() { JackMidiData *data = static_cast (apiData_); MidiOutJack::closePort(); // Cleanup jack_ringbuffer_free( data->buffSize ); jack_ringbuffer_free( data->buffMessage ); if ( data->client ) { jack_client_close( data->client ); } #ifdef HAVE_SEMAPHORE sem_destroy(&data->sem_cleanup); sem_destroy(&data->sem_needpost); #endif delete data; } void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName ) { JackMidiData *data = static_cast (apiData_); connect(); // Creating new port if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openPort: JACK error creating port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); connected_ = true; } void MidiOutJack :: openVirtualPort( const std::string &portName ) { JackMidiData *data = static_cast (apiData_); connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } unsigned int MidiOutJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); connect(); if ( !data->client ) return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); if ( ports == NULL ) return 0; while ( ports[count] != NULL ) count++; free( ports ); return count; } std::string MidiOutJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); std::string retStr(""); connect(); // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); // Check port validity if ( ports == NULL) { errorString_ = "MidiOutJack::getPortName: no ports available!"; error( RtMidiError::WARNING, errorString_ ); return retStr; } if ( ports[portNumber] == NULL) { std::ostringstream ost; ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); } else retStr.assign( ports[portNumber] ); free( ports ); return retStr; } void MidiOutJack :: closePort() { JackMidiData *data = static_cast (apiData_); if ( data->port == NULL ) return; #ifdef HAVE_SEMAPHORE struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) != -1) { ts.tv_sec += 1; // wait max one second sem_post(&data->sem_needpost); sem_timedwait(&data->sem_cleanup, &ts); } #endif jack_port_unregister( data->client, data->port ); data->port = NULL; connected_ = false; } void MidiOutJack:: setClientName( const std::string& ) { errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutJack :: setPortName( const std::string &portName ) { JackMidiData *data = static_cast (apiData_); #ifdef JACK_HAS_PORT_RENAME jack_port_rename( data->client, data->port, portName.c_str() ); #else jack_port_set_name( data->port, portName.c_str() ); #endif } void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) { int nBytes = static_cast(size); JackMidiData *data = static_cast (apiData_); // Write full message to buffer jack_ringbuffer_write( data->buffMessage, ( const char * ) message, nBytes ); jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); } #endif // __UNIX_JACK__ BambooTracker-0.3.5/BambooTracker/midi/RtMidi/RtMidi.h000066400000000000000000000576471362177441300224530ustar00rootroot00000000000000/**********************************************************************/ /*! \class RtMidi \brief An abstract base class for realtime MIDI input/output. This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2017 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**********************************************************************/ /*! \file RtMidi.h */ #ifndef RTMIDI_H #define RTMIDI_H #if defined _WIN32 || defined __CYGWIN__ #define RTMIDI_DLL_PUBLIC #else #if __GNUC__ >= 4 #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) ) #else #define RTMIDI_DLL_PUBLIC #endif #endif #define RTMIDI_VERSION "3.0.0" #include #include #include #include /************************************************************************/ /*! \class RtMidiError \brief Exception handling class for RtMidi. The RtMidiError class is quite simple but it does allow errors to be "caught" by RtMidiError::Type. See the RtMidi documentation to know which methods can throw an RtMidiError. */ /************************************************************************/ class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception { public: //! Defined RtMidiError types. enum Type { WARNING, /*!< A non-critical error. */ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ MEMORY_ERROR, /*!< An error occured during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ DRIVER_ERROR, /*!< A system driver error occured. */ SYSTEM_ERROR, /*!< A system error occured. */ THREAD_ERROR /*!< A thread error occured. */ }; //! The constructor. RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {} //! The destructor. virtual ~RtMidiError( void ) throw() {} //! Prints thrown error message to stderr. virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } //! Returns the thrown error message type. virtual const Type& getType(void) const throw() { return type_; } //! Returns the thrown error message string. virtual const std::string& getMessage(void) const throw() { return message_; } //! Returns the thrown error message as a c-style string. virtual const char* what( void ) const throw() { return message_.c_str(); } protected: std::string message_; Type type_; }; //! RtMidi error callback function prototype. /*! \param type Type of error. \param errorText Error description. Note that class behaviour is undefined after a critical error (not a warning) is reported. */ typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData ); class MidiApi; class RTMIDI_DLL_PUBLIC RtMidi { public: //! MIDI API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ RTMIDI_DUMMY /*!< A compilable but non-functional API. */ }; //! A static function to determine the current RtMidi version. static std::string getVersion( void ) throw(); //! A static function to determine the available compiled MIDI APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ) throw(); //! Pure virtual openPort() function. virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual openVirtualPort() function. virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual getPortCount() function. virtual unsigned int getPortCount() = 0; //! Pure virtual getPortName() function. virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; //! Pure virtual closePort() function. virtual void closePort( void ) = 0; void setClientName( const std::string &clientName ); void setPortName( const std::string &portName ); //! Returns true if a port is open and false if not. /*! Note that this only applies to connections made with the openPort() function, not to virtual ports. */ virtual bool isPortOpen( void ) const = 0; //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; protected: RtMidi(); virtual ~RtMidi(); MidiApi *rtapi_; }; /**********************************************************************/ /*! \class RtMidiIn \brief A realtime MIDI input class. This class provides a common, platform-independent API for realtime MIDI input. It allows access to a single MIDI input port. Incoming MIDI messages are either saved to a queue for retrieval using the getMessage() function or immediately passed to a user-specified callback function. Create multiple instances of this class to connect to more than one MIDI device at the same time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also possible to open a virtual input port to which other MIDI software clients can connect. by Gary P. Scavone, 2003-2017. */ /**********************************************************************/ // **************************************************************** // // // RtMidiIn and RtMidiOut class declarations. // // RtMidiIn / RtMidiOut are "controllers" used to select an available // MIDI input or output interface. They present common APIs for the // user to call but all functionality is implemented by the classes // MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut // each create an instance of a MidiInApi or MidiOutApi subclass based // on the user's API choice. If no choice is made, they attempt to // make a "logical" API selection. // // **************************************************************** // class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { public: //! User callback function type definition. typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData); //! Default constructor that allows an optional api, client name and queue size. /*! An exception will be thrown if a MIDI system initialization error occurs. The queue size defines the maximum number of messages that can be held in the MIDI queue (when not using a callback function). If the queue size limit is reached, incoming messages will be ignored. If no API argument is specified and multiple API support has been compiled, the default order of use is ALSA, JACK (Linux) and CORE, JACK (OS-X). \param api An optional API id can be specified. \param clientName An optional client name can be specified. This will be used to group the ports that are created by the application. \param queueSizeLimit An optional size of the MIDI input queue can be specified. */ RtMidiIn( RtMidi::Api api=UNSPECIFIED, const std::string& clientName = "RtMidi Input Client", unsigned int queueSizeLimit = 100 ); //! If a MIDI connection is still open, it will be closed by the destructor. ~RtMidiIn ( void ) throw(); //! Returns the MIDI API specifier for the current instance of RtMidiIn. RtMidi::Api getCurrentApi( void ) throw(); //! Open a MIDI input connection given by enumeration number. /*! \param portNumber An optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened. \param portName An optional name for the application port that is used to connect to portId can be specified. */ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Input" ) ); //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI input port to which other software applications can connect. This type of functionality is currently only supported by the Macintosh OS-X, any JACK, and Linux ALSA APIs (the function returns an error for the other APIs). \param portName An optional name for the application port that is used to connect to portId can be specified. */ void openVirtualPort( const std::string &portName = std::string( "RtMidi Input" ) ); //! Set a callback function to be invoked for incoming MIDI messages. /*! The callback function will be called whenever an incoming MIDI message is received. While not absolutely necessary, it is best to set the callback function before opening a MIDI port to avoid leaving some messages in the queue. \param callback A callback function must be given. \param userData Optionally, a pointer to additional data can be passed to the callback function whenever it is called. */ void setCallback( RtMidiCallback callback, void *userData = 0 ); //! Cancel use of the current callback function (if one exists). /*! Subsequent incoming MIDI messages will be written to the queue and can be retrieved with the \e getMessage function. */ void cancelCallback(); //! Close an open MIDI connection (if one exists). void closePort( void ); //! Returns true if a port is open and false if not. /*! Note that this only applies to connections made with the openPort() function, not to virtual ports. */ virtual bool isPortOpen() const; //! Return the number of available MIDI input ports. /*! \return This function returns the number of MIDI ports of the selected API. */ unsigned int getPortCount(); //! Return a string identifier for the specified MIDI input port number. /*! \return The name of the port with the given Id is returned. \retval An empty string is returned if an invalid port specifier is provided. User code should assume a UTF-8 encoding. */ std::string getPortName( unsigned int portNumber = 0 ); //! Specify whether certain MIDI message types should be queued or ignored during input. /*! By default, MIDI timing and active sensing messages are ignored during message input because of their relative high data rates. MIDI sysex messages are ignored by default as well. Variable values of "true" imply that the respective message type will be ignored. */ void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true ); //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. /*! This function returns immediately whether a new message is available or not. A valid message is indicated by a non-zero vector size. An exception is thrown if an error occurs during message retrieval or an input connection was not previously established. */ double getMessage( std::vector *message ); //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected: void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); }; /**********************************************************************/ /*! \class RtMidiOut \brief A realtime MIDI output class. This class provides a common, platform-independent API for MIDI output. It allows one to probe available MIDI output ports, to connect to one such port, and to send MIDI bytes immediately over the connection. Create multiple instances of this class to connect to more than one MIDI device at the same time. With the OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a virtual port to which other MIDI software clients can connect. by Gary P. Scavone, 2003-2017. */ /**********************************************************************/ class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi { public: //! Default constructor that allows an optional client name. /*! An exception will be thrown if a MIDI system initialization error occurs. If no API argument is specified and multiple API support has been compiled, the default order of use is ALSA, JACK (Linux) and CORE, JACK (OS-X). */ RtMidiOut( RtMidi::Api api=UNSPECIFIED, const std::string& clientName = "RtMidi Output Client" ); //! The destructor closes any open MIDI connections. ~RtMidiOut( void ) throw(); //! Returns the MIDI API specifier for the current instance of RtMidiOut. RtMidi::Api getCurrentApi( void ) throw(); //! Open a MIDI output connection. /*! An optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened. An exception is thrown if an error occurs while attempting to make the port connection. */ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Output" ) ); //! Close an open MIDI connection (if one exists). void closePort( void ); //! Returns true if a port is open and false if not. /*! Note that this only applies to connections made with the openPort() function, not to virtual ports. */ virtual bool isPortOpen() const; //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI output port to which other software applications can connect. This type of functionality is currently only supported by the Macintosh OS-X, Linux ALSA and JACK APIs (the function does nothing with the other APIs). An exception is thrown if an error occurs while attempting to create the virtual port. */ void openVirtualPort( const std::string &portName = std::string( "RtMidi Output" ) ); //! Return the number of available MIDI output ports. unsigned int getPortCount( void ); //! Return a string identifier for the specified MIDI port type and number. /*! \return The name of the port with the given Id is returned. \retval An empty string is returned if an invalid port specifier is provided. User code should assume a UTF-8 encoding. */ std::string getPortName( unsigned int portNumber = 0 ); //! Immediately send a single message out an open MIDI output port. /*! An exception is thrown if an error occurs during output or an output connection was not previously established. */ void sendMessage( const std::vector *message ); //! Immediately send a single message out an open MIDI output port. /*! An exception is thrown if an error occurs during output or an output connection was not previously established. \param message A pointer to the MIDI message as raw bytes \param size Length of the MIDI message in bytes */ void sendMessage( const unsigned char *message, size_t size ); //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected: void openMidiApi( RtMidi::Api api, const std::string &clientName ); }; // **************************************************************** // // // MidiInApi / MidiOutApi class declarations. // // Subclasses of MidiInApi and MidiOutApi contain all API- and // OS-specific code necessary to fully implement the RtMidi API. // // Note that MidiInApi and MidiOutApi are abstract base classes and // cannot be explicitly instantiated. RtMidiIn and RtMidiOut will // create instances of a MidiInApi or MidiOutApi subclass. // // **************************************************************** // class RTMIDI_DLL_PUBLIC MidiApi { public: MidiApi(); virtual ~MidiApi(); virtual RtMidi::Api getCurrentApi( void ) = 0; virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0; virtual void openVirtualPort( const std::string &portName ) = 0; virtual void closePort( void ) = 0; virtual void setClientName( const std::string &clientName ) = 0; virtual void setPortName( const std::string &portName ) = 0; virtual unsigned int getPortCount( void ) = 0; virtual std::string getPortName( unsigned int portNumber ) = 0; inline bool isPortOpen() const { return connected_; } void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ); //! A basic error reporting function for RtMidi classes. void error( RtMidiError::Type type, std::string errorString ); protected: virtual void initialize( const std::string& clientName ) = 0; void *apiData_; bool connected_; std::string errorString_; RtMidiErrorCallback errorCallback_; bool firstErrorOccurred_; void *errorCallbackUserData_; }; class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi { public: MidiInApi( unsigned int queueSizeLimit ); virtual ~MidiInApi( void ); void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); void cancelCallback( void ); virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); double getMessage( std::vector *message ); // A MIDI structure used internally by the class to store incoming // messages. Each message represents one and only one MIDI message. struct MidiMessage { std::vector bytes; //! Time in seconds elapsed since the previous message double timeStamp; // Default constructor. MidiMessage() :bytes(0), timeStamp(0.0) {} }; struct MidiQueue { unsigned int front; unsigned int back; unsigned int ringSize; MidiMessage *ring; // Default constructor. MidiQueue() :front(0), back(0), ringSize(0), ring(0) {} bool push(const MidiMessage&); bool pop(std::vector*, double*); unsigned int size(unsigned int *back=0, unsigned int *front=0); }; // The RtMidiInData structure is used to pass private class data to // the MIDI input handling function or thread. struct RtMidiInData { MidiQueue queue; MidiMessage message; unsigned char ignoreFlags; bool doInput; bool firstMessage; void *apiData; bool usingCallback; RtMidiIn::RtMidiCallback userCallback; void *userData; bool continueSysex; // Default constructor. RtMidiInData() : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false), userCallback(0), userData(0), continueSysex(false) {} }; protected: RtMidiInData inputData_; }; class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi { public: MidiOutApi( void ); virtual ~MidiOutApi( void ); virtual void sendMessage( const unsigned char *message, size_t size ) = 0; }; // **************************************************************** // // // Inline RtMidiIn and RtMidiOut definitions. // // **************************************************************** // inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); } inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); } inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } inline double RtMidiIn :: getMessage( std::vector *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } inline void RtMidiOut :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } inline void RtMidiOut :: sendMessage( const std::vector *message ) { ((MidiOutApi *)rtapi_)->sendMessage( &message->at(0), message->size() ); } inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { ((MidiOutApi *)rtapi_)->sendMessage( message, size ); } inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } #endif BambooTracker-0.3.5/BambooTracker/midi/RtMidi/RtMidi.pri000066400000000000000000000005211362177441300227710ustar00rootroot00000000000000SOURCES += \ $$PWD/RtMidi.cpp HEADERS += \ $$PWD/RtMidi.h linux { DEFINES += __LINUX_ALSA__ LIBS += -lasound } win32 { DEFINES += __WINDOWS_MM__ LIBS += -lwinmm } macx { DEFINES += __MACOSX_CORE__ LIBS += -framework CoreMIDI -framework CoreAudio -framework CoreFoundation } DEFINES += __RTMIDI_DUMMY__ BambooTracker-0.3.5/BambooTracker/midi/midi.cpp000066400000000000000000000132301362177441300213240ustar00rootroot00000000000000#include "midi.hpp" #include "midi_def.h" #include std::unique_ptr MidiInterface::instance_; MidiInterface &MidiInterface::instance() { if (instance_) return *instance_; MidiInterface *out = new MidiInterface; instance_.reset(out); return *out; } MidiInterface::MidiInterface() : hasInitializedMidiIn_(false), hasInitializedMidiOut_(false), hasOpenInputPort_(false), hasOpenOutputPort_(false) { switchApi(RtMidi::UNSPECIFIED); } MidiInterface::~MidiInterface() { } RtMidi::Api MidiInterface::currentApi() const { return inputClient_->getCurrentApi(); } void MidiInterface::switchApi(RtMidi::Api api) { if (inputClient_ && api == inputClient_->getCurrentApi()) return; RtMidiIn *inputClient = nullptr; try { inputClient = new RtMidiIn(api, MIDI_INP_CLIENT_NAME, MidiBufferSize); hasInitializedMidiIn_ = true; } catch (RtMidiError &error) { hasInitializedMidiIn_ = false; fprintf(stderr, "Cannot initialize MIDI In.\n"); } if (!inputClient) inputClient = new RtMidiIn(RtMidi::RTMIDI_DUMMY, MIDI_INP_CLIENT_NAME, MidiBufferSize); inputClient->ignoreTypes(MIDI_INP_IGNORE_SYSEX, MIDI_INP_IGNORE_TIME, MIDI_INP_IGNORE_SENSE); inputClient_.reset(inputClient); inputClient->setErrorCallback(&onMidiError, this); inputClient->setCallback(&onMidiInput, this); hasOpenInputPort_ = false; RtMidiOut *outputClient = nullptr; try { outputClient = new RtMidiOut(api, MIDI_OUT_CLIENT_NAME); hasInitializedMidiOut_ = false; } catch (RtMidiError &error) { hasInitializedMidiOut_ = true; fprintf(stderr, "Cannot initialize MIDI Out.\n"); } if (!outputClient) outputClient = new RtMidiOut(RtMidi::RTMIDI_DUMMY, MIDI_OUT_CLIENT_NAME); outputClient_.reset(outputClient); outputClient->setErrorCallback(&onMidiError, this); hasOpenOutputPort_ = false; } bool MidiInterface::supportsVirtualPort() const { switch (currentApi()) { case RtMidi::MACOSX_CORE: case RtMidi::LINUX_ALSA: case RtMidi::UNIX_JACK: return true; default: return false; } } std::vector MidiInterface::getRealInputPorts() { RtMidiIn &client = *inputClient_; unsigned count = client.getPortCount(); std::vector ports; ports.reserve(count); for (unsigned i = 0; i < count; ++i) ports.push_back(client.getPortName(i)); return ports; } std::vector MidiInterface::getRealOutputPorts() { RtMidiOut &client = *outputClient_; unsigned count = client.getPortCount(); std::vector ports; ports.reserve(count); for (unsigned i = 0; i < count; ++i) ports.push_back(client.getPortName(i)); return ports; } bool MidiInterface::hasInitializedInput() const { return hasInitializedMidiIn_; } bool MidiInterface::hasInitializedOutput() const { return hasInitializedMidiOut_; } void MidiInterface::closeInputPort() { RtMidiIn &client = *inputClient_; if (hasOpenInputPort_) { client.closePort(); hasOpenInputPort_ = false; } } void MidiInterface::closeOutputPort() { RtMidiOut &client = *outputClient_; if (hasOpenOutputPort_) { client.closePort(); hasOpenOutputPort_ = false; } } void MidiInterface::openInputPort(unsigned port) { RtMidiIn &client = *inputClient_; closeInputPort(); std::string name = MIDI_INP_PORT_NAME; if (port == ~0u) { client.openVirtualPort(name); hasOpenInputPort_ = true; } else { client.openPort(port, name); hasOpenInputPort_ = client.isPortOpen(); } } void MidiInterface::openOutputPort(unsigned port) { RtMidiOut &client = *outputClient_; closeOutputPort(); std::string name = MIDI_OUT_PORT_NAME; if (port == ~0u) { client.openVirtualPort(name); hasOpenOutputPort_ = true; } else { client.openPort(port, name); hasOpenOutputPort_ = client.isPortOpen(); } } void MidiInterface::openInputPortByName(const std::string &portName) { std::vector ports = getRealInputPorts(); for (unsigned i = 0, n = ports.size(); i < n; ++i) { if (ports[i] == portName) { openInputPort(i); return; } } } void MidiInterface::openOutputPortByName(const std::string &portName) { std::vector ports = getRealOutputPorts(); for (unsigned i = 0, n = ports.size(); i < n; ++i) { if (ports[i] == portName) { openOutputPort(i); return; } } } void MidiInterface::sendMessage(const uint8_t *data, size_t length) { RtMidiOut &client = *outputClient_; if (hasOpenOutputPort_) client.sendMessage(data, length); } void MidiInterface::onMidiError(RtMidiError::Type type, const std::string &text, void *user_data) { MidiInterface *self = reinterpret_cast(user_data); (void)type; (void)self; fprintf(stderr, "[Midi Out] %s\n", text.c_str()); } void MidiInterface::installInputHandler(InputHandler *handler, void *user_data) { std::lock_guard lock(inputHandlersMutex_); inputHandlers_.push_back(std::make_pair(handler, user_data)); } void MidiInterface::uninstallInputHandler(InputHandler *handler, void *user_data) { std::lock_guard lock(inputHandlersMutex_); for (size_t i = 0, n = inputHandlers_.size(); i < n; ++i) { bool match = inputHandlers_[i].first == handler && inputHandlers_[i].second == user_data; if (match) { inputHandlers_.erase(inputHandlers_.begin() + i); return; } } } void MidiInterface::onMidiInput(double timestamp, std::vector *message, void *user_data) { MidiInterface *self = reinterpret_cast(user_data); std::unique_lock lock(self->inputHandlersMutex_, std::try_to_lock); if (!lock.owns_lock()) return; const uint8_t *msg = message->data(); size_t len = message->size(); for (size_t i = 0, n = self->inputHandlers_.size(); i < n; ++i) self->inputHandlers_[i].first(timestamp, msg, len, self->inputHandlers_[i].second); } BambooTracker-0.3.5/BambooTracker/midi/midi.hpp000066400000000000000000000030171362177441300213330ustar00rootroot00000000000000#pragma once #include "RtMidi/RtMidi.h" #include #include #include #include #include class MidiInterface { public: static MidiInterface &instance(); ~MidiInterface(); private: MidiInterface(); public: RtMidi::Api currentApi() const; void switchApi(RtMidi::Api api); bool supportsVirtualPort() const; std::vector getRealInputPorts(); std::vector getRealOutputPorts(); bool hasInitializedInput() const; bool hasInitializedOutput() const; void closeInputPort(); void closeOutputPort(); void openInputPort(unsigned port); void openOutputPort(unsigned port); void openInputPortByName(const std::string &portName); void openOutputPortByName(const std::string &portName); typedef void (InputHandler)(double, const uint8_t *, size_t, void *); void installInputHandler(InputHandler *handler, void *userData); void uninstallInputHandler(InputHandler *handler, void *userData); void sendMessage(const uint8_t *data, size_t length); private: static void onMidiError(RtMidiError::Type type, const std::string &text, void *userData); static void onMidiInput(double timestamp, std::vector *message, void *userData); std::unique_ptr inputClient_; std::unique_ptr outputClient_; bool hasInitializedMidiIn_, hasInitializedMidiOut_; bool hasOpenInputPort_, hasOpenOutputPort_; std::mutex inputHandlersMutex_; std::vector> inputHandlers_; static std::unique_ptr instance_; }; BambooTracker-0.3.5/BambooTracker/midi/midi_def.h000066400000000000000000000005421362177441300216110ustar00rootroot00000000000000#pragma once enum { MidiBufferSize = 8192 }; #define MIDI_INP_CLIENT_NAME "BambooTracker Rx" #define MIDI_OUT_CLIENT_NAME "BambooTracker Tx" #define MIDI_INP_PORT_NAME "BambooTracker MIDI In" #define MIDI_OUT_PORT_NAME "BambooTracker MIDI Out" #define MIDI_INP_IGNORE_SYSEX false #define MIDI_INP_IGNORE_TIME false #define MIDI_INP_IGNORE_SENSE true BambooTracker-0.3.5/BambooTracker/misc.hpp000066400000000000000000000100271362177441300204210ustar00rootroot00000000000000#pragma once #include #include #if !defined(DECL_MAYBE_UNUSED) && defined(__GNUC__) #define DECL_MAYBE_UNUSED __attribute__((unused)) #elif !defined(DECL_MAYBE_UNUSED) #define DECL_MAYBE_UNUSED #endif enum class SoundSource : int { FM = 1, SSG = 2, DRUM = 4 }; enum class SongType : int { Standard, FM3chExpanded }; enum class Note : int { C = 0, CS = 32, D = 64, DS = 96, E = 128, F = 160, FS = 192, G = 224, GS = 256, A = 288, AS = 320, B = 352 }; enum class FMEnvelopeTextType : int { Skip, AL, FB, AR1, DR1, SR1, RR1, SL1, TL1, KS1, ML1, DT1, AR2, DR2, SR2, RR2, SL2, TL2, KS2, ML2, DT2, AR3, DR3, SR3, RR3, SL3, TL3, KS3, ML3, DT3, AR4, DR4, SR4, RR4, SL4, TL4, KS4, ML4, DT4 }; enum class FMOperatorType : int { All, Op1, Op2, Op3, Op4 }; enum class SSGWaveFormType : int { UNSET = -1, SQUARE = 0, TRIANGLE = 1, SAW = 2, INVSAW = 3, SQM_TRIANGLE = 4, SQM_SAW = 5, SQM_INVSAW = 6 }; enum class EffectDisplayControl { Unset, ReverseFMVolumeDelay, ReverseFMBrightness }; enum class RealChipInterface : int { NONE = 0, SCCI = 1, C86CTL = 2 }; DECL_MAYBE_UNUSED static std::pair noteNumberToOctaveAndNote(int num) { if (num < 0) return std::make_pair(0, Note::C); int oct = num / 12; if (oct > 7) return std::make_pair(7, Note::B); Note note; switch (num % 12) { case 0: note = Note::C; break; case 1: note = Note::CS; break; case 2: note = Note::D; break; case 3: note = Note::DS; break; case 4: note = Note::E; break; case 5: note = Note::F; break; case 6: note = Note::FS; break; case 7: note = Note::G; break; case 8: note = Note::GS; break; case 9: note = Note::A; break; case 10: note = Note::AS; break; case 11: note = Note::B; break; } return std::make_pair(oct, note); } DECL_MAYBE_UNUSED static int octaveAndNoteToNoteNumber(int octave, Note note) { int ret = 12 * octave; switch (note) { case Note::C: ret += 0; break; case Note::CS: ret += 1; break; case Note::D: ret += 2; break; case Note::DS: ret += 3; break; case Note::E: ret += 4; break; case Note::F: ret += 5; break; case Note::FS: ret += 6; break; case Note::G: ret += 7; break; case Note::GS: ret += 8; break; case Note::A: ret += 9; break; case Note::AS: ret += 10; break; case Note::B: ret += 11; break; } return ret; } DECL_MAYBE_UNUSED static int ctohex(const char c) { if (c == '0') return 0; else if (c == '1') return 1; else if (c == '2') return 2; else if (c == '3') return 3; else if (c == '4') return 4; else if (c == '5') return 5; else if (c == '6') return 6; else if (c == '7') return 7; else if (c == '8') return 8; else if (c == '9') return 9; else if (c == 'A') return 10; else if (c == 'B') return 11; else if (c == 'C') return 12; else if (c == 'D') return 13; else if (c == 'E') return 14; else if (c == 'F') return 15; else return -1; } DECL_MAYBE_UNUSED static uint8_t uitobcd(const uint8_t v) { if (v > 99) throw std::out_of_range("Out of range."); uint8_t high = v / 10; uint8_t low = v % 10; return static_cast(high << 4) + low; } DECL_MAYBE_UNUSED static uint8_t bcdtoui(const uint8_t v) { uint8_t high = v >> 4; uint8_t low = v & 0x0f; return high * 10 + low; } DECL_MAYBE_UNUSED inline static size_t getFMChannelCount(SongType type) { switch (type) { case SongType::Standard: return 6; case SongType::FM3chExpanded: return 9; default: throw std::invalid_argument("Invalid SongType."); } } DECL_MAYBE_UNUSED inline static bool isModulatedWaveFormSSG(SSGWaveFormType type) { switch (type) { case SSGWaveFormType::SQUARE: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: return false; case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: return true; default: throw std::invalid_argument("Invalid SSGWaveFormType"); } } DECL_MAYBE_UNUSED inline static bool isModulatedWaveFormSSG(int id) { try { return isModulatedWaveFormSSG(static_cast(id)); } catch (...) { throw std::invalid_argument("Invalid id"); } } BambooTracker-0.3.5/BambooTracker/module/000077500000000000000000000000001362177441300202425ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/module/effect.cpp000066400000000000000000000121531362177441300222040ustar00rootroot00000000000000#include "effect.hpp" EffectType Effect::toEffectType(SoundSource src, std::string id) { if (id == "00") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::Arpeggio; default: return EffectType::NoEffect; } } else if (id == "01") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::PortamentoUp; default: return EffectType::NoEffect; } } else if (id == "02") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::PortamentoDown; default: return EffectType::NoEffect; } } else if (id == "03") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::TonePortamento; default: return EffectType::NoEffect; } } else if (id == "04") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::Vibrato; default: return EffectType::NoEffect; } } else if (id == "07") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::Tremolo; default: return EffectType::NoEffect; } } else if (id == "08") { switch (src) { case SoundSource::FM: case SoundSource::DRUM: return EffectType::Pan; default: return EffectType::NoEffect; } } else if (id == "0A") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::VolumeSlide; default: return EffectType::NoEffect; } } else if (id == "0B") { return EffectType::PositionJump; } else if (id == "0C") { return EffectType::SongEnd; } else if (id == "0D") { return EffectType::PatternBreak; } else if (id == "0F") { return EffectType::SpeedTempoChange; } else if (id == "0G") { return EffectType::NoteDelay; } else if (id == "0H") { switch (src) { case SoundSource::SSG: return EffectType::AutoEnvelope; default: return EffectType::NoEffect; } } else if (id == "0I") { switch (src) { case SoundSource::SSG: return EffectType::HardEnvHighPeriod; default: return EffectType::NoEffect; } } else if (id == "0J") { switch (src) { case SoundSource::SSG: return EffectType::HardEnvLowPeriod; default: return EffectType::NoEffect; } } else if (id == "0O") { return EffectType::Groove; } else if (id == "0P") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::Detune; default: return EffectType::NoEffect; } } else if (id == "0Q") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::NoteSlideUp; default: return EffectType::NoEffect; } } else if (id == "0R") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::NoteSlideDown; default: return EffectType::NoEffect; } } else if (id == "0S") { return EffectType::NoteCut; } else if (id == "0T") { switch (src) { case SoundSource::FM: case SoundSource::SSG: return EffectType::TransposeDelay; default: return EffectType::NoEffect; } } else if (id == "0V") { switch (src) { case SoundSource::SSG: return EffectType::ToneNoiseMix; case SoundSource::DRUM: return EffectType::MasterVolume; default: return EffectType::NoEffect; } } else if (id == "0W") { switch (src) { case SoundSource::SSG: return EffectType::NoisePitch; default: return EffectType::NoEffect; } } else if (id == "0X") { return EffectType::RegisterAddress0; } else if (id == "0Y") { return EffectType::RegisterAddress1; } else if (id == "0Z") { return EffectType::RegisterValue; } else if (id == "B0") { switch (src) { case SoundSource::FM: return EffectType::Brightness; default: return EffectType::NoEffect; } } else if (id == "FB") { switch (src) { case SoundSource::FM: return EffectType::FBControl; default: return EffectType::NoEffect; } } else if (id == "ML") { switch (src) { case SoundSource::FM: return EffectType::MLControl; default: return EffectType::NoEffect; } } else if (id == "RR") { switch (src) { case SoundSource::FM: return EffectType::RRControl; default: return EffectType::NoEffect; } } else { switch (id.front()) { case 'A': switch (src) { case SoundSource::FM: return EffectType::ARControl; default: return EffectType::NoEffect; } case 'D': switch (src) { case SoundSource::FM: return EffectType::DRControl; default: return EffectType::NoEffect; } case 'M': return EffectType::VolumeDelay; case 'T': switch (src) { case SoundSource::FM: return EffectType::TLControl; default: return EffectType::NoEffect; } default: return EffectType::NoEffect; } } } Effect Effect::makeEffectData(SoundSource src, std::string id, int value) { if (value == -1) return { EffectType::NoEffect, -1 }; EffectType type = Effect::toEffectType(src, id); int v; switch (type) { case EffectType::NoEffect: v = -1; break; case EffectType::VolumeDelay: case EffectType::TLControl: case EffectType::ARControl: case EffectType::DRControl: v = (ctohex(id[1]) << 8) | value; break; default: v = value; } return { type, v }; } BambooTracker-0.3.5/BambooTracker/module/effect.hpp000066400000000000000000000013531362177441300222110ustar00rootroot00000000000000#pragma once #include #include "misc.hpp" enum class EffectType { NoEffect, Arpeggio, PortamentoUp, PortamentoDown, TonePortamento, Vibrato, Tremolo, Pan, VolumeSlide, PositionJump, SongEnd, PatternBreak, SpeedTempoChange, NoteDelay, Groove, Detune, NoteSlideUp, NoteSlideDown, NoteCut, TransposeDelay, MasterVolume, VolumeDelay, ToneNoiseMix, NoisePitch, HardEnvHighPeriod, HardEnvLowPeriod, AutoEnvelope, FBControl, TLControl, MLControl, ARControl, DRControl, RRControl, RegisterAddress0, RegisterAddress1, RegisterValue, Brightness }; struct Effect { EffectType type; int value; static EffectType toEffectType(SoundSource src, std::string id); static Effect makeEffectData(SoundSource src, std::string id, int value); }; BambooTracker-0.3.5/BambooTracker/module/groove.cpp000066400000000000000000000004351362177441300222510ustar00rootroot00000000000000#include "groove.hpp" #include Groove::Groove() : seq_{ 6, 6 } { } Groove::Groove(std::vector seq) : seq_(std::move(seq)) { } void Groove::setSequrnce(std::vector seq) { seq_ = std::move(seq); } std::vector Groove::getSequence() const { return seq_; } BambooTracker-0.3.5/BambooTracker/module/groove.hpp000066400000000000000000000003271362177441300222560ustar00rootroot00000000000000#pragma once #include class Groove { public: Groove(); Groove(std::vector seq); void setSequrnce(std::vector seq); std::vector getSequence() const; private: std::vector seq_; }; BambooTracker-0.3.5/BambooTracker/module/module.cpp000066400000000000000000000103511362177441300222330ustar00rootroot00000000000000#include "module.hpp" #include #include Module::Module(std::string filePath, std::string title, std::string author, std::string copyright, std::string comment, unsigned int tickFreq) : filePath_(filePath), title_(title), author_(author), copyright_(copyright), comment_(comment), tickFreq_(tickFreq), stepHl1Dist_(4), stepHl2Dist_(16), mixType_(MixerType::PC_9821_PC_9801_86), customLevelFM_(0), customLevelSSG_(0) { songs_.emplace_back(0); grooves_.emplace_back(); } void Module::setFilePath(std::string path) { filePath_ = path; } std::string Module::getFilePath() const { return filePath_; } void Module::setTitle(std::string title) { title_ = title; } std::string Module::getTitle() const { return title_; } void Module::setAuthor(std::string author) { author_ = author; } std::string Module::getAuthor() const { return author_; } void Module::setCopyright(std::string copyright) { copyright_ = copyright; } std::string Module::getCopyright() const { return copyright_; } void Module::setComment(std::string comment) { comment_ = comment; } std::string Module::getComment() const { return comment_; } void Module::setTickFrequency(unsigned int freq) { tickFreq_ = freq; } unsigned int Module::getTickFrequency() const { return tickFreq_; } void Module::setStepHighlight1Distance(size_t dist) { stepHl1Dist_ = dist; } size_t Module::getStepHighlight1Distance() const { return stepHl1Dist_; } void Module::setStepHighlight2Distance(size_t dist) { stepHl2Dist_ = dist; } size_t Module::getStepHighlight2Distance() const { return stepHl2Dist_; } size_t Module::getSongCount() const { return songs_.size(); } size_t Module::getGrooveCount() const { return grooves_.size(); } void Module::setMixerType(MixerType type) { mixType_ = type; } MixerType Module::getMixerType() const { return mixType_; } void Module::setCustomMixerFMLevel(double level) { customLevelFM_ = level; } double Module::getCustomMixerFMLevel() const { return customLevelFM_; } void Module::setCustomMixerSSGLevel(double level) { customLevelSSG_ = level; } double Module::getCustomMixerSSGLevel() const { return customLevelSSG_; } void Module::addSong(SongType songType, std::string title) { int n = static_cast(songs_.size()); songs_.emplace_back(n, songType, title); } void Module::addSong(int n, SongType songType, std::string title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize) { if (n < static_cast(songs_.size())) songs_.at(static_cast(n)) = Song(n, songType, title, isUsedTempo, tempo, groove, speed, defaultPatternSize); else songs_.emplace_back( n, songType, title, isUsedTempo, tempo, groove, speed, defaultPatternSize); } void Module::sortSongs(std::vector numbers) { std::vector newSongs; newSongs.reserve(songs_.size()); for (auto& n : numbers) { auto it = std::make_move_iterator(songs_.begin() + n); it->setNumber(static_cast(newSongs.size())); newSongs.push_back(*it); } songs_.assign(std::make_move_iterator(newSongs.begin()), std::make_move_iterator(newSongs.end())); songs_.shrink_to_fit(); } Song& Module::getSong(int num) { auto it = std::find_if(songs_.begin(), songs_.end(), [num](Song& s) { return s.getNumber() == num; }); return *it; } void Module::addGroove() { grooves_.emplace_back(); } void Module::removeGroove(int num) { grooves_.erase(grooves_.begin() + num); } void Module::setGroove(int num, std::vector seq) { grooves_.at(static_cast(num)).setSequrnce(seq); } void Module::setGrooves(std::vector> seqs) { grooves_.clear(); for (auto& seq : seqs) { grooves_.emplace_back(seq); } } Groove& Module::getGroove(int num) { return grooves_.at(static_cast(num)); } std::unordered_set Module::getRegisterdInstruments() const { std::unordered_set set; for (auto& song : songs_) { for (auto& n : song.getRegisteredInstruments()) { set.insert(n); } } return set; } void Module::clearUnusedPatterns() { for (auto& song : songs_) song.clearUnusedPatterns(); } void Module::replaceDuplicateInstrumentsInPatterns(std::unordered_map map) { for (auto& song : songs_) song.replaceDuplicateInstrumentsInPatterns(map); } BambooTracker-0.3.5/BambooTracker/module/module.hpp000066400000000000000000000044421362177441300222440ustar00rootroot00000000000000#pragma once #include #include #include #include #include "song.hpp" #include "groove.hpp" enum class MixerType : int { UNSPECIFIED = 0, CUSTOM = 1, PC_9821_PC_9801_86 = 2, PC_9821_SPEAK_BOARD = 3, PC_8801_VA2 = 4, PC_8801_MKII_SR = 5 }; class Module { public: Module(std::string filePath = "", std::string title = u8"", std::string author = u8"", std::string copyright = u8"", std::string comment = u8"", unsigned int tickFreq = 60); void setFilePath(std::string path); std::string getFilePath() const; void setTitle(std::string title); std::string getTitle() const; void setAuthor(std::string author); std::string getAuthor() const; void setCopyright(std::string copyright); std::string getCopyright() const; void setComment(std::string comment); std::string getComment() const; void setTickFrequency(unsigned int freq); unsigned int getTickFrequency() const; void setStepHighlight1Distance(size_t dist); size_t getStepHighlight1Distance() const; void setStepHighlight2Distance(size_t dist); size_t getStepHighlight2Distance() const; size_t getSongCount() const; size_t getGrooveCount() const; void setMixerType(MixerType type); MixerType getMixerType() const; void setCustomMixerFMLevel(double level); double getCustomMixerFMLevel() const; void setCustomMixerSSGLevel(double level); double getCustomMixerSSGLevel() const; void addSong(SongType songType, std::string title); void addSong(int n, SongType songType, std::string title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize); void sortSongs(std::vector numbers); Song& getSong(int num); void addGroove(); void removeGroove(int num); void setGroove(int num, std::vector seq); void setGrooves(std::vector> seqs); Groove& getGroove(int num); std::unordered_set getRegisterdInstruments() const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(std::unordered_map map); private: std::string filePath_; std::string title_; std::string author_; std::string copyright_; std::string comment_; unsigned int tickFreq_; size_t stepHl1Dist_, stepHl2Dist_; std::vector songs_; std::vector grooves_; MixerType mixType_; double customLevelFM_, customLevelSSG_; }; BambooTracker-0.3.5/BambooTracker/module/pattern.cpp000066400000000000000000000043611362177441300224270ustar00rootroot00000000000000#include "pattern.hpp" #include "effect.hpp" #include "misc.hpp" #include Pattern::Pattern(int n, size_t defSize) : num_(n), size_(defSize), steps_(defSize), usedCnt_(0) { } Pattern::Pattern(int n, size_t size, std::vector steps) : num_(n), size_(size), steps_(steps), usedCnt_(0) { } void Pattern::setNumber(int n) { num_ = n; } int Pattern::getNumber() const { return num_; } int Pattern::usedCountUp() { return ++usedCnt_; } int Pattern::usedCountDown() { return --usedCnt_; } int Pattern::getUsedCount() const { return usedCnt_; } Step& Pattern::getStep(int n) { return steps_.at(static_cast(n)); } size_t Pattern::getSize() const { for (size_t i = 0; i < size_; ++i) { for (int j = 0; j < 4; ++j) { switch (Effect::makeEffectData( // "SoundSource::FM" is dummy SoundSource::FM, steps_[i].getEffectID(j), steps_[i].getEffectValue(j) ).type) { case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: return i + 1; default: break; } } } return size_; } void Pattern::changeSize(size_t size) { if (0 < size && size <= 256) { size_ = size; if (steps_.size() < size) steps_.resize(size); } } void Pattern::insertStep(int n) { if (n < static_cast(size_)) steps_.emplace(steps_.begin() + n); } void Pattern::deletePreviousStep(int n) { if (!n) return; steps_.erase(steps_.begin() + n - 1); if (steps_.size() < size_) steps_.resize(size_); } bool Pattern::existCommand() const { auto&& it = std::find_if(steps_.begin(), steps_.begin() + static_cast(size_), [](const Step& step) { return step.existCommand(); }); return (it != steps_.end()); } std::vector Pattern::getEditedStepIndices() const { std::vector list; for (size_t i = 0; i < size_; ++i) { if (steps_.at(i).existCommand()) list.push_back(static_cast(i)); } return list; } std::unordered_set Pattern::getRegisteredInstruments() const { std::unordered_set set; for (size_t i = 0; i < size_; ++i) { int n = steps_.at(i).getInstrumentNumber(); if (n > -1) set.insert(n); } return set; } Pattern Pattern::clone(int asNumber) { return Pattern(asNumber, size_, steps_); } void Pattern::clear() { steps_ = std::vector(size_); } BambooTracker-0.3.5/BambooTracker/module/pattern.hpp000066400000000000000000000013351362177441300224320ustar00rootroot00000000000000#pragma once #include #include #include #include "step.hpp" class Pattern { public: Pattern(int n, size_t defSize); void setNumber(int n); int getNumber() const; int usedCountUp(); int usedCountDown(); int getUsedCount() const; Step& getStep(int n); size_t getSize() const; void changeSize(size_t size); void insertStep(int n); void deletePreviousStep(int n); bool existCommand() const; std::vector getEditedStepIndices() const; std::unordered_set getRegisteredInstruments() const; Pattern clone(int asNumber); void clear(); private: int num_; size_t size_; std::vector steps_; int usedCnt_; Pattern(int n, size_t size, std::vector steps); }; BambooTracker-0.3.5/BambooTracker/module/song.cpp000066400000000000000000000076611362177441300217260ustar00rootroot00000000000000#include "song.hpp" #include Song::Song(int number, SongType songType, std::string title, bool isUsedTempo, int tempo, int groove, int speed, size_t defaultPatternSize) : num_(number), type_(songType), title_(title), isUsedTempo_(isUsedTempo), tempo_(tempo), groove_(groove), speed_(speed), defPtnSize_(defaultPatternSize) { switch (songType) { case SongType::Standard: tracks_.reserve(15); for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i, SoundSource::FM, i, defaultPatternSize); } for (int i = 0; i < 3; ++i) { tracks_.emplace_back(i + 6, SoundSource::SSG, i, defaultPatternSize); } for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i + 9, SoundSource::DRUM, i, defaultPatternSize); } break; case SongType::FM3chExpanded: tracks_.reserve(18); for (int i = 0; i < 9; ++i) { int ch = (i < 3) ? i : (i < 6) ? (i + 3) : (i - 3); tracks_.emplace_back(i, SoundSource::FM, ch, defaultPatternSize); } for (int i = 0; i < 3; ++i) { tracks_.emplace_back(i + 9, SoundSource::SSG, i, defaultPatternSize); } for (int i = 0; i < 6; ++i) { tracks_.emplace_back(i + 12, SoundSource::DRUM, i, defaultPatternSize); } break; } } void Song::setNumber(int n) { num_ = n; } int Song::getNumber() const { return num_; } void Song::setTitle(std::string title) { title_ = title; } std::string Song::getTitle() const { return title_; } void Song::setTempo(int tempo) { tempo_ = tempo; } int Song::getTempo() const { return tempo_; } void Song::setGroove(int groove) { groove_ = groove; } int Song::getGroove() const { return groove_; } void Song::toggleTempoOrGroove(bool isUsedTempo) { isUsedTempo_ = isUsedTempo; } bool Song::isUsedTempo() const { return isUsedTempo_; } void Song::setSpeed(int speed) { speed_ = speed; } int Song::getSpeed() const { return speed_; } void Song::setDefaultPatternSize(size_t size) { defPtnSize_ = size; for (auto& t : tracks_) { t.changeDefaultPatternSize(size); } } size_t Song::getDefaultPatternSize() const { return defPtnSize_; } size_t Song::getPatternSizeFromOrderNumber(int order) { if (static_cast(getOrderSize()) <= order) return 0; // Ilegal value size_t size = 0; for (auto& t : tracks_) { size_t ptnSize = t.getPatternFromOrderNumber(order).getSize(); size = !size ? ptnSize : std::min(size, ptnSize); } return size; } SongStyle Song::getStyle() const { SongStyle style; style.type = type_; style.trackAttribs = getTrackAttributes(); return style; } std::vector Song::getTrackAttributes() const { std::vector ret; std::transform(tracks_.begin(), tracks_.end(), std::back_inserter(ret), [](const Track& track) { return track.getAttribute(); }); return ret; } Track& Song::getTrack(int num) { return tracks_.at(static_cast(num)); } std::vector Song::getOrderData(int order) { std::vector ret; for (auto& track : tracks_) { ret.push_back(track.getOrderData(order)); } return ret; } size_t Song::getOrderSize() const { return tracks_[0].getOrderSize(); } bool Song::canAddNewOrder() const { return getOrderSize() < 256; } void Song::insertOrderBelow(int order) { if (!canAddNewOrder()) return; for (auto& track : tracks_) { track.insertOrderBelow(order); } } void Song::deleteOrder(int order) { for (auto& track : tracks_) { track.deleteOrder(order); } } void Song::swapOrder(int a, int b) { for (auto& track : tracks_) { track.swapOrder(a, b); } } std::unordered_set Song::getRegisteredInstruments() const { std::unordered_set set; for (auto& track : tracks_) { for (auto& n : track.getRegisteredInstruments()) { set.insert(n); } } return set; } void Song::clearUnusedPatterns() { for (auto& track : tracks_) track.clearUnusedPatterns(); } void Song::replaceDuplicateInstrumentsInPatterns(std::unordered_map map) { for (auto& track : tracks_) track.replaceDuplicateInstrumentsInPatterns(map); } BambooTracker-0.3.5/BambooTracker/module/song.hpp000066400000000000000000000030661362177441300217260ustar00rootroot00000000000000#pragma once #include #include #include #include #include "track.hpp" #include "misc.hpp" struct SongStyle; class Song { public: Song(int number, SongType songType = SongType::Standard, std::string title = u8"", bool isUsedTempo = true, int tempo = 150, int groove = 0, int speed = 6, size_t defaultPatternSize = 64); void setNumber(int n); int getNumber() const; void setTitle(std::string title); std::string getTitle() const; void setTempo(int tempo); int getTempo() const; void setGroove(int groove); int getGroove() const; void toggleTempoOrGroove(bool isUsedTempo); bool isUsedTempo() const; void setSpeed(int speed); int getSpeed() const; void setDefaultPatternSize(size_t size); size_t getDefaultPatternSize() const; size_t getPatternSizeFromOrderNumber(int order); SongStyle getStyle() const; std::vector getTrackAttributes() const; Track& getTrack(int num); std::vector getOrderData(int order); size_t getOrderSize() const; bool canAddNewOrder() const; void insertOrderBelow(int order); void deleteOrder(int order); void swapOrder(int a, int b); std::unordered_set getRegisteredInstruments() const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(std::unordered_map map); private: int num_; SongType type_; std::string title_; bool isUsedTempo_; int tempo_; int groove_; int speed_; size_t defPtnSize_; std::vector tracks_; }; struct SongStyle { SongType type; std::vector trackAttribs; }; BambooTracker-0.3.5/BambooTracker/module/step.cpp000066400000000000000000000022201362177441300217150ustar00rootroot00000000000000#include "step.hpp" Step::Step() : noteNum_(-1), instNum_(-1), vol_(-1) { for (size_t i = 0; i < 4; ++i) { effID_[i] = "--"; effVal_[i] = -1; } } int Step::getNoteNumber() const { return noteNum_; } void Step::setNoteNumber(int num) { noteNum_ = num; } int Step::getInstrumentNumber() const { return instNum_; } void Step::setInstrumentNumber(int num) { instNum_ = num; } int Step::getVolume() const { return vol_; } void Step::setVolume(int volume) { vol_ = volume; } std::string Step::getEffectID(int n) const { return effID_[n]; } void Step::setEffectID(int n, std::string str) { effID_[n] = str; } int Step::getEffectValue(int n) const { return effVal_[n]; } void Step::setEffectValue(int n, int v) { effVal_[n] = v; } int Step::checkEffectID(std::string str) const { for (int i = 0; i < 4; ++i) { if (effID_[i] == str && effVal_[i] != -1) return i; } return -1; } bool Step::existCommand() const { if (noteNum_ != -1) return true; if (instNum_ != -1) return true; if (vol_ != -1) return true; for (int i = 0; i < 4; ++i) { if (effID_[i] != "--") return true; if (effVal_[i] != -1) return true; } return false; } BambooTracker-0.3.5/BambooTracker/module/step.hpp000066400000000000000000000016751362177441300217370ustar00rootroot00000000000000#pragma once #include class Step { public: Step(); int getNoteNumber() const; void setNoteNumber(int num); int getInstrumentNumber() const; void setInstrumentNumber(int num); int getVolume() const; void setVolume(int volume); std::string getEffectID(int n) const; void setEffectID(int n, std::string str); int getEffectValue(int n) const; void setEffectValue(int n, int v); /// NOTE: Deprecated int checkEffectID(std::string str) const; bool existCommand() const; private: /// noteNum_ /// 0<=: note number (key on) /// -1: none /// -2: key off /// -3: echo previous note /// -4: echo 2 notes before /// -5: echo 3 notes before /// -6: echo 4 notes before int noteNum_; /// instNum_ /// 0<=: instrument number /// -1: none int instNum_; /// vol_ /// 0<=: volume level /// -1: none int vol_; std::string effID_[4]; /// effVal_ /// 0<=: effect value /// -1: none int effVal_[4]; }; BambooTracker-0.3.5/BambooTracker/module/track.cpp000066400000000000000000000074531362177441300220630ustar00rootroot00000000000000#include "track.hpp" #include Track::Track(int number, SoundSource source, int channelInSource, int defPattenSize) : attrib_(std::make_unique()), effetDisplayWidth_(0) { attrib_->number = number; attrib_->source = source; attrib_->channelInSource = channelInSource; for (int i = 0; i < 256; ++i) { patterns_.emplace_back(i, defPattenSize); } patterns_[0].usedCountUp(); order_.push_back(0); // Set first order } Track::Track(const Track& other) : attrib_(std::make_unique()) { attrib_->number = other.attrib_->number; attrib_->source = other.attrib_->source; attrib_->channelInSource = other.attrib_->channelInSource; order_ = other.order_; patterns_ = other.patterns_; effetDisplayWidth_ = other.effetDisplayWidth_; } TrackAttribute Track::getAttribute() const { return *attrib_; } OrderData Track::getOrderData(int order) { OrderData res; res.trackAttribute = getAttribute(); res.order = order; res.patten = order_.at(static_cast(order)); return res; } size_t Track::getOrderSize() const { return order_.size(); } Pattern& Track::getPattern(int num) { return patterns_.at(static_cast(num)); } Pattern& Track::getPatternFromOrderNumber(int num) { return getPattern(order_.at(static_cast(num))); } int Track::searchFirstUneditedUnusedPattern() const { for (size_t i = 0; i < patterns_.size(); ++i) { if (!patterns_[i].existCommand() && !patterns_[i].getUsedCount()) return static_cast(i); } return -1; } int Track::clonePattern(int num) { int n = searchFirstUneditedUnusedPattern(); if (n == -1) return num; else { patterns_.at(static_cast(n)) = patterns_.at(static_cast(num)).clone(n); return n; } } std::vector Track::getEditedPatternIndices() const { std::vector list; for (size_t i = 0; i < 256; ++i) { if (patterns_[i].existCommand()) list.push_back(static_cast(i)); } return list; } std::unordered_set Track::getRegisteredInstruments() const { std::unordered_set set; for (auto& pattern : patterns_) { for (auto& n : pattern.getRegisteredInstruments()) { set.insert(n); } } return set; } void Track::registerPatternToOrder(int order, int pattern) { patterns_.at(static_cast(pattern)).usedCountUp(); patterns_.at(static_cast(order_.at(static_cast(order)))).usedCountDown(); order_.at(static_cast(order)) = pattern; } void Track::insertOrderBelow(int order) { int n = searchFirstUneditedUnusedPattern(); if (n == -1) n = 255; if (order == static_cast(order_.size()) - 1) order_.push_back(n); else order_.insert(order_.begin() + order + 1, n); patterns_[static_cast(n)].usedCountUp(); } void Track::deleteOrder(int order) { patterns_.at(static_cast(order_.at(static_cast(order)))).usedCountDown(); order_.erase(order_.begin() + order); } void Track::swapOrder(int a, int b) { std::swap(order_.at(static_cast(a)), order_.at((static_cast(b)))); } void Track::changeDefaultPatternSize(size_t size) { for (auto& ptn : patterns_) { ptn.changeSize(size); } } void Track::setEffectDisplayWidth(size_t w) { effetDisplayWidth_ = w; } size_t Track::getEffectDisplayWidth() const { return effetDisplayWidth_; } void Track::clearUnusedPatterns() { for (size_t i = 0; i < 256; ++i) { if (!patterns_[i].getUsedCount() && patterns_[i].existCommand()) patterns_[i].clear(); } } void Track::replaceDuplicateInstrumentsInPatterns(std::unordered_map map) { for (size_t i = 0; i < 256; ++i) { Pattern& pattern = patterns_[i]; if (pattern.existCommand()) { for (size_t i = 0; i < pattern.getSize(); ++i) { Step& step = pattern.getStep(static_cast(i)); int inst = step.getInstrumentNumber(); if (map.count(inst)) step.setInstrumentNumber(map[inst]); } } } } BambooTracker-0.3.5/BambooTracker/module/track.hpp000066400000000000000000000025371362177441300220660ustar00rootroot00000000000000#pragma once #include #include #include #include #include "pattern.hpp" #include "misc.hpp" struct TrackAttribute; struct OrderData; class Track { public: Track(int number, SoundSource source, int channelInSource, int defPattenSize); Track(const Track& other); TrackAttribute getAttribute() const; OrderData getOrderData(int order); size_t getOrderSize() const; Pattern& getPattern(int num); Pattern& getPatternFromOrderNumber(int num); int searchFirstUneditedUnusedPattern() const; int clonePattern(int num); std::vector getEditedPatternIndices() const; std::unordered_set getRegisteredInstruments() const; void registerPatternToOrder(int order, int pattern); void insertOrderBelow(int order); void deleteOrder(int order); void swapOrder(int a, int b); void changeDefaultPatternSize(size_t size); void setEffectDisplayWidth(size_t w); size_t getEffectDisplayWidth() const; void clearUnusedPatterns(); void replaceDuplicateInstrumentsInPatterns(std::unordered_map map); private: std::unique_ptr attrib_; std::vector order_; std::vector patterns_; size_t effetDisplayWidth_; }; struct TrackAttribute { int number; SoundSource source; int channelInSource; }; struct OrderData { TrackAttribute trackAttribute; int order; int patten; }; BambooTracker-0.3.5/BambooTracker/opna_controller.cpp000066400000000000000000003201341362177441300226640ustar00rootroot00000000000000#include "opna_controller.hpp" #include #include "pitch_converter.hpp" OPNAController::OPNAController(chip::Emu emu, int clock, int rate, int duration) : mode_(SongType::Standard) { opna_ = std::make_unique(emu, clock, rate, duration, std::make_unique(), std::make_unique()); for (int ch = 0; ch < 6; ++ch) { fmOpEnables_[ch] = 0xf; isMuteFM_[ch] = false; for (auto ep : getFMEnvelopeParametersForOperator(FMOperatorType::All)) opSeqItFM_[ch].emplace(ep, nullptr); } for (int ch = 0; ch < 3; ++ch) { isMuteSSG_[ch] = false; } for (int ch = 0; ch < 6; ++ch) { isMuteDrum_[ch] = false; } initChip(); outputHistory_.reset(new int16_t[2 * OUTPUT_HISTORY_SIZE]{}); outputHistoryReady_.reset(new int16_t[2 * OUTPUT_HISTORY_SIZE]{}); outputHistoryIndex_ = 0; } /********** Reset and initialize **********/ void OPNAController::reset() { opna_->reset(); initChip(); std::fill(&outputHistory_[0], &outputHistory_[2 * OUTPUT_HISTORY_SIZE], 0); std::fill(&outputHistoryReady_[0], &outputHistoryReady_[2 * OUTPUT_HISTORY_SIZE], 0); } void OPNAController::initChip() { opna_->setRegister(0x29, 0x80); // Init interrupt / YM2608 mode registerSetBuf_.clear(); initFM(); initSSG(); initDrum(); } /********** Forward instrument sequence **********/ void OPNAController::tickEvent(SoundSource src, int ch) { switch (src) { case SoundSource::FM: tickEventFM(ch); break; case SoundSource::SSG: tickEventSSG(ch); break; case SoundSource::DRUM: break; } } /********** Direct register set **********/ void OPNAController::sendRegisterAddress(int bank, int address) { address = (bank << 8) | address; if (registerSetBuf_.empty() || registerSetBuf_.back().hasCompleted) registerSetBuf_.push_back({ address, 0, false }); else registerSetBuf_.back().address = address; } void OPNAController::sendRegisterValue(int value) { if (!registerSetBuf_.empty()) { auto& unit = registerSetBuf_.back(); unit.value = value; unit.hasCompleted = true; } } /********** Update register states after tick process **********/ void OPNAController::updateRegisterStates() { updateKeyOnOffStatusDrum(); // Check direct register set if (!registerSetBuf_.empty()) { if (!registerSetBuf_.back().hasCompleted) registerSetBuf_.pop_back(); for (auto& unit : registerSetBuf_) { opna_->setRegister(static_cast(unit.address), static_cast(unit.value)); } registerSetBuf_.clear(); } } /********** Real chip interface **********/ void OPNAController::useSCCI(scci::SoundInterfaceManager* manager) { opna_->useSCCI(manager); } bool OPNAController::isUsedSCCI() const { return opna_->isUsedSCCI(); } void OPNAController::useC86CTL(C86ctlBase* base) { opna_->useC86CTL(base); } bool OPNAController::isUsedC86CTL() const { return opna_->isUsedC86CTL(); } /********** Stream samples **********/ void OPNAController::getStreamSamples(int16_t* container, size_t nSamples) { opna_->mix(container, nSamples); size_t nHistory = std::min(nSamples, OUTPUT_HISTORY_SIZE); fillOutputHistory(&container[2 * (nSamples - nHistory)], nHistory); } void OPNAController::getOutputHistory(int16_t* container) { std::lock_guard lock(outputHistoryReadyMutex_); int16_t *history = outputHistoryReady_.get(); std::copy(history, &history[2 * OUTPUT_HISTORY_SIZE], container); } void OPNAController::fillOutputHistory(const int16_t* outputs, size_t nSamples) { int16_t *history = outputHistory_.get(); size_t historyIndex = outputHistoryIndex_; // copy as many as possible to the back size_t backCapacity = OUTPUT_HISTORY_SIZE - historyIndex; size_t nBack = std::min(nSamples, backCapacity); std::copy(outputs, &outputs[2 * nBack], &history[2 * historyIndex]); // copy the rest to the front std::copy(&outputs[2 * nBack], &outputs[2 * nSamples], history); // update the write position historyIndex = (historyIndex + nSamples) % OUTPUT_HISTORY_SIZE; outputHistoryIndex_ = historyIndex; // if no one holds the ready buffer, update the contents std::unique_lock lock(outputHistoryReadyMutex_, std::try_to_lock); if (lock.owns_lock()) transferReadyHistory(); } void OPNAController::transferReadyHistory() { const int16_t *src = outputHistory_.get(); int16_t *dst = outputHistoryReady_.get(); size_t index = outputHistoryIndex_; // copy the back, and then the front std::copy(&src[2 * index], &src[2 * OUTPUT_HISTORY_SIZE], dst); std::copy(&src[0], &src[2 * index], &dst[2 * (OUTPUT_HISTORY_SIZE - index)]); } /********** Chip mode **********/ void OPNAController::setMode(SongType mode) { mode_ = mode; reset(); } SongType OPNAController::getMode() const { return mode_; } /********** Stream details **********/ int OPNAController::getRate() const { return opna_->getRate(); } void OPNAController::setRate(int rate) { opna_->setRate(rate); } int OPNAController::getDuration() const { return static_cast(opna_->getMaxDuration()); } void OPNAController::setDuration(int duration) { opna_->setMaxDuration(static_cast(duration)); } void OPNAController::setMasterVolume(int percentage) { opna_->setMasterVolume(percentage); } void OPNAController::setExportContainer(std::shared_ptr cntr) { opna_->setExportContainer(cntr); } //---------- FM ----------// /********** Key on-off **********/ void OPNAController::keyOnFM(int ch, Note note, int octave, int pitch, bool isJam) { if (isMuteFM(ch)) return; updateEchoBufferFM(ch, octave, note, pitch); if (isTonePortamentoFM(ch)) { keyToneFM_[ch].pitch += (sumNoteSldFM_[ch] + transposeFM_[ch]); } else { keyToneFM_[ch] = baseToneFM_[ch].front(); sumPitchFM_[ch] = 0; sumVolSldFM_[ch] = 0; } if (tmpVolFM_[ch] != -1) { setVolumeFM(ch, baseVolFM_[ch]); } if (!noteSldFMSetFlag_[ch]) { nsItFM_[ch].reset(); } noteSldFMSetFlag_[ch] = false; needToneSetFM_[ch] = true; sumNoteSldFM_[ch] = 0; transposeFM_[ch] = 0; setFrontFMSequences(ch); hasPreSetTickEventFM_[ch] = isJam; if (!isTonePortamentoFM(ch)) { uint8_t chdata = getFMKeyOnOffChannelMask(ch); switch (mode_) { case SongType::Standard: { if (isKeyOnFM_[ch]) opna_->setRegister(0x28, chdata); // Key off else isKeyOnFM_[ch] = true; opna_->setRegister(0x28, static_cast(fmOpEnables_[ch] << 4) | chdata); break; } case SongType::FM3chExpanded: { uint8_t slot = 0; switch (ch) { case 2: case 6: case 7: case 8: { bool prev = isKeyOnFM_[ch]; isKeyOnFM_[ch] = true; slot = fmOpEnables_[2] & ( static_cast(isKeyOnFM_[2]) | (static_cast(isKeyOnFM_[6]) << 1) | (static_cast(isKeyOnFM_[7]) << 2) | (static_cast(isKeyOnFM_[8]) << 3) ); if (prev) { // Key off uint8_t mask = 0; switch (ch) { case 2: mask = 0xe; break; case 6: mask = 0xd; break; case 7: mask = 0xb; break; case 8: mask = 0x7; break; default: break; } opna_->setRegister(0x28, static_cast(((slot & mask) << 4)) | chdata); } break; } default: slot = fmOpEnables_[ch]; if (isKeyOnFM_[ch]) opna_->setRegister(0x28, chdata); // Key off else isKeyOnFM_[ch] = true; break; } opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } } } } void OPNAController::keyOnFM(int ch, int echoBuf) { ToneDetail& td = baseToneFM_[ch].at(static_cast(echoBuf)); if (td.octave == -1) return; keyOnFM(ch, td.note, td.octave, td.pitch); } void OPNAController::keyOffFM(int ch, bool isJam) { if (!isKeyOnFM_[ch]) { tickEventFM(ch); return; } releaseStartFMSequences(ch); hasPreSetTickEventFM_[ch] = isJam; isKeyOnFM_[ch] = false; uint8_t chdata = getFMKeyOnOffChannelMask(ch); switch (mode_) { case SongType::Standard: { opna_->setRegister(0x28, chdata); break; } case SongType::FM3chExpanded: { switch (ch) { case 2: case 6: case 7: case 8: { uint8_t slot = fmOpEnables_[2] & ( static_cast(isKeyOnFM_[2]) | (static_cast(isKeyOnFM_[6]) << 1) | (static_cast(isKeyOnFM_[7]) << 2) | (static_cast(isKeyOnFM_[8]) << 3) ); opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } default: opna_->setRegister(0x28, chdata); break; } break; } } } // Change register only void OPNAController::resetFMChannelEnvelope(int ch) { keyOffFM(ch); hasResetEnvFM_[ch] = true; if (mode_ == SongType::FM3chExpanded && toInternalFMChannel(ch) == 2) { FMEnvelopeParameter param; switch (ch) { case 2: param = FMEnvelopeParameter::RR1; break; case 6: param = FMEnvelopeParameter::RR2; break; case 7: param = FMEnvelopeParameter::RR3; break; case 8: param = FMEnvelopeParameter::RR4; break; default: throw std::out_of_range("out of range."); } int prev = envFM_[2]->getParameterValue(param); writeFMEnveropeParameterToRegister(2, param, 127); envFM_[2]->setParameterValue(param, prev); } else { int prev = envFM_[ch]->getParameterValue(FMEnvelopeParameter::RR1); writeFMEnveropeParameterToRegister(ch, FMEnvelopeParameter::RR1, 127); envFM_[ch]->setParameterValue(FMEnvelopeParameter::RR1, prev); prev = envFM_[ch]->getParameterValue(FMEnvelopeParameter::RR2); writeFMEnveropeParameterToRegister(ch, FMEnvelopeParameter::RR2, 127); envFM_[ch]->setParameterValue(FMEnvelopeParameter::RR2, prev); prev = envFM_[ch]->getParameterValue(FMEnvelopeParameter::RR3); writeFMEnveropeParameterToRegister(ch, FMEnvelopeParameter::RR3, 127); envFM_[ch]->setParameterValue(FMEnvelopeParameter::RR3, prev); prev = envFM_[ch]->getParameterValue(FMEnvelopeParameter::RR4); writeFMEnveropeParameterToRegister(ch, FMEnvelopeParameter::RR4, 127); envFM_[ch]->setParameterValue(FMEnvelopeParameter::RR4, prev); } } void OPNAController::updateEchoBufferFM(int ch, int octave, Note note, int pitch) { baseToneFM_[ch].pop_back(); baseToneFM_[ch].push_front({ octave, note, pitch }); } /********** Set instrument **********/ /// NOTE: inst != nullptr void OPNAController::setInstrumentFM(int ch, std::shared_ptr inst) { int inch = toInternalFMChannel(ch); FMOperatorType opType = toChannelOperatorType(ch); if (!refInstFM_[inch] || !refInstFM_[inch]->isRegisteredWithManager() || refInstFM_[inch]->getNumber() != inst->getNumber()) { refInstFM_[inch] = inst; writeFMEnvelopeToRegistersFromInstrument(inch); fmOpEnables_[inch] = static_cast(inst->getOperatorEnabled(0)) | static_cast(inst->getOperatorEnabled(1) << 1) | static_cast(inst->getOperatorEnabled(2) << 2) | static_cast(inst->getOperatorEnabled(3) << 3); } else { if (isFBCtrlFM_[inch]) { isFBCtrlFM_[inch] = false; writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::FB, inst->getEnvelopeParameter(FMEnvelopeParameter::FB)); } for (int op = 0; op < 4; ++op) { if (isTLCtrlFM_[inch][op] || isBrightFM_[inch][op]) { isTLCtrlFM_[inch][op] = false; isBrightFM_[inch][op] = false; FMEnvelopeParameter tl = getParameterTL(op); writeFMEnveropeParameterToRegister(inch, tl, inst->getEnvelopeParameter(tl)); } if (isMLCtrlFM_[inch][op]) { isMLCtrlFM_[inch][op] = false; FMEnvelopeParameter ml = getParameterML(op); writeFMEnveropeParameterToRegister(inch, ml, inst->getEnvelopeParameter(ml)); } if (isARCtrlFM_[inch][op]) { isARCtrlFM_[inch][op] = false; FMEnvelopeParameter ar = getParameterAR(op); writeFMEnveropeParameterToRegister(inch, ar, inst->getEnvelopeParameter(ar)); } if (isDRCtrlFM_[inch][op]) { isDRCtrlFM_[inch][op] = false; FMEnvelopeParameter dr = getParameterDR(op); writeFMEnveropeParameterToRegister(inch, dr, inst->getEnvelopeParameter(dr)); } if (isRRCtrlFM_[inch][op]) { isRRCtrlFM_[inch][op] = false; FMEnvelopeParameter rr = getParameterRR(op); writeFMEnveropeParameterToRegister(inch, rr, inst->getEnvelopeParameter(rr)); } } restoreFMEnvelopeFromReset(ch); } if (isKeyOnFM_[ch] && lfoStartCntFM_[inch] == -1) writeFMLFOAllRegisters(inch); for (auto& p : getFMEnvelopeParametersForOperator(opType)) { if (refInstFM_[inch]->getOperatorSequenceEnabled(p)) { opSeqItFM_[inch].at(p) = refInstFM_[inch]->getOperatorSequenceSequenceIterator(p); switch (p) { case FMEnvelopeParameter::FB: isFBCtrlFM_[inch] = false; break; case FMEnvelopeParameter::TL1: isTLCtrlFM_[inch][0] = false; isBrightFM_[inch][0] = false; break; case FMEnvelopeParameter::TL2: isTLCtrlFM_[inch][1] = false; isBrightFM_[inch][1] = false; break; case FMEnvelopeParameter::TL3: isTLCtrlFM_[inch][2] = false; isBrightFM_[inch][2] = false; break; case FMEnvelopeParameter::TL4: isTLCtrlFM_[inch][3] = false; isBrightFM_[inch][3] = false; break; case FMEnvelopeParameter::ML1: isMLCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::ML2: isMLCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::ML3: isMLCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::ML4: isMLCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::AR1: isARCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::AR2: isARCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::AR3: isARCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::AR4: isARCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::DR1: isDRCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::DR2: isDRCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::DR3: isDRCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::DR4: isDRCtrlFM_[inch][3] = false; break; case FMEnvelopeParameter::RR1: isRRCtrlFM_[inch][0] = false; break; case FMEnvelopeParameter::RR2: isRRCtrlFM_[inch][1] = false; break; case FMEnvelopeParameter::RR3: isRRCtrlFM_[inch][2] = false; break; case FMEnvelopeParameter::RR4: isRRCtrlFM_[inch][3] = false; break; default: break; } } else { opSeqItFM_[inch].at(p).reset(); } } if (!isArpEffFM_[ch]) { if (refInstFM_[inch]->getArpeggioEnabled(opType)) arpItFM_[ch] = refInstFM_[inch]->getArpeggioSequenceIterator(opType); else arpItFM_[ch].reset(); } if (refInstFM_[inch]->getPitchEnabled(opType)) ptItFM_[ch] = refInstFM_[inch]->getPitchSequenceIterator(opType); else ptItFM_[ch].reset(); setInstrumentFMProperties(ch); checkLFOUsed(); } void OPNAController::updateInstrumentFM(int instNum) { int cnt = static_cast(getFMChannelCount(mode_)); for (int ch = 0; ch < cnt; ++ch) { int inch = toInternalFMChannel(ch); if (refInstFM_[inch] && refInstFM_[inch]->isRegisteredWithManager() && refInstFM_[inch]->getNumber() == instNum) { writeFMEnvelopeToRegistersFromInstrument(inch); if (isKeyOnFM_[ch] && lfoStartCntFM_[inch] == -1) writeFMLFOAllRegisters(inch); FMOperatorType opType = toChannelOperatorType(ch); for (auto& p : getFMEnvelopeParametersForOperator(opType)) { if (!refInstFM_[inch]->getOperatorSequenceEnabled(p)) opSeqItFM_[inch].at(p).reset(); } if (!refInstFM_[inch]->getArpeggioEnabled(opType)) arpItFM_[ch].reset(); if (!refInstFM_[inch]->getPitchEnabled(opType)) ptItFM_[ch].reset(); setInstrumentFMProperties(ch); } } checkLFOUsed(); } void OPNAController::updateInstrumentFMEnvelopeParameter(int envNum, FMEnvelopeParameter param) { for (int ch = 0; ch < 6; ++ch) { if (refInstFM_[ch] && refInstFM_[ch]->getEnvelopeNumber() == envNum) { writeFMEnveropeParameterToRegister(ch, param, refInstFM_[ch]->getEnvelopeParameter(param)); } } } void OPNAController::setInstrumentFMOperatorEnabled(int envNum, int opNum) { int chsize = static_cast(getFMChannelCount(mode_)); for (int ch = 0; ch < chsize; ++ch) { int inch = toInternalFMChannel(ch); if (refInstFM_[inch] && refInstFM_[inch]->getEnvelopeNumber() == envNum) { bool enabled = refInstFM_[inch]->getOperatorEnabled(opNum); envFM_[inch]->setOperatorEnabled(opNum, enabled); if (enabled) { fmOpEnables_[inch] |= (1 << opNum); } else { fmOpEnables_[inch] &= ~(1 << opNum); } if (isKeyOnFM_[ch]) { uint8_t chdata = getFMKeyOnOffChannelMask(ch); switch (mode_) { case SongType::Standard: { opna_->setRegister(0x28, static_cast(fmOpEnables_[inch] << 4) | chdata); break; } case SongType::FM3chExpanded: { uint8_t slot; if (inch == 2) { slot = fmOpEnables_[2] & ( static_cast(isKeyOnFM_[2]) | (static_cast(isKeyOnFM_[6]) << 1) | (static_cast(isKeyOnFM_[7]) << 2) | (static_cast(isKeyOnFM_[8]) << 3) ); } else { slot = fmOpEnables_[inch]; } opna_->setRegister(0x28, static_cast(slot << 4) | chdata); break; } } } } } } void OPNAController::updateInstrumentFMLFOParameter(int lfoNum, FMLFOParameter param) { for (int ch = 0; ch < 6; ++ch) { if (refInstFM_[ch] && refInstFM_[ch]->getLFOEnabled() && refInstFM_[ch]->getLFONumber() == lfoNum) { writeFMLFORegister(ch, param); } } } /********** Set volume **********/ void OPNAController::setVolumeFM(int ch, int volume) { baseVolFM_[ch] = volume; tmpVolFM_[ch] = -1; if (refInstFM_[toInternalFMChannel(ch)]) updateFMVolume(ch); // Change TL } void OPNAController::setTemporaryVolumeFM(int ch, int volume) { tmpVolFM_[ch] = volume; if (refInstFM_[toInternalFMChannel(ch)]) updateFMVolume(ch); // Change TL } void OPNAController::updateFMVolume(int ch) { int inch = toInternalFMChannel(ch); switch (toChannelOperatorType(ch)) { case FMOperatorType::All: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL1, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL1)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL2, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL2)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL3, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL3)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL4, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL4)); break; case FMOperatorType::Op1: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL1, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL1)); break; case FMOperatorType::Op2: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL2, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL2)); break; case FMOperatorType::Op3: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL3, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL3)); break; case FMOperatorType::Op4: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::TL4, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL4)); break; } } void OPNAController::setMasterVolumeFM(double dB) { opna_->setVolumeFM(dB); } /********** Set pan **********/ void OPNAController::setPanFM(int ch, int value) { int inch = toInternalFMChannel(ch); panFM_[inch] = static_cast(value); uint32_t bch = getFMChannelOffset(ch); // Bank and channel offset uint8_t data = static_cast(value << 6); if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { data |= (refInstFM_[inch]->getLFOParameter(FMLFOParameter::AMS) << 4); data |= refInstFM_[inch]->getLFOParameter(FMLFOParameter::PMS); } opna_->setRegister(0xb4 + bch, data); } /********** Set effect **********/ void OPNAController::setArpeggioEffectFM(int ch, int second, int third) { if (second || third) { arpItFM_[ch] = std::make_unique(second, third); isArpEffFM_[ch] = true; } else { int inch = toInternalFMChannel(ch); if (refInstFM_[inch]) { FMOperatorType op = toChannelOperatorType(ch); if (!refInstFM_[inch]->getArpeggioEnabled(op)) arpItFM_[ch].reset(); else arpItFM_[ch] = refInstFM_[inch]->getArpeggioSequenceIterator(op); } isArpEffFM_[ch] = false; } } void OPNAController::setPortamentoEffectFM(int ch, int depth, bool isTonePortamento) { prtmFM_[ch] = depth; isTonePrtmFM_[ch] = depth ? isTonePortamento : false; } void OPNAController::setVibratoEffectFM(int ch, int period, int depth) { if (period && depth) vibItFM_[ch] = std::make_unique(period, depth); else vibItFM_[ch].reset(); } void OPNAController::setTremoloEffectFM(int ch, int period, int depth) { if (period && depth) treItFM_[ch] = std::make_unique(period, depth); else treItFM_[ch].reset(); } void OPNAController::setVolumeSlideFM(int ch, int depth, bool isUp) { volSldFM_[ch] = depth * (isUp ? -1 : 1); } void OPNAController::setDetuneFM(int ch, int pitch) { detuneFM_[ch] = pitch; needToneSetFM_[ch] = true; } void OPNAController::setNoteSlideFM(int ch, int speed, int seminote) { if (seminote) { nsItFM_[ch] = std::make_unique(speed, seminote); noteSldFMSetFlag_[ch] = true; } else nsItFM_[ch].reset(); } void OPNAController::setTransposeEffectFM(int ch, int seminote) { transposeFM_[ch] += (seminote * 32); needToneSetFM_[ch] = true; } void OPNAController::setFBControlFM(int ch, int value) { int inch = toInternalFMChannel(ch); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::FB, value); isFBCtrlFM_[inch] = true; opSeqItFM_[inch].at(FMEnvelopeParameter::FB).reset(); } void OPNAController::setTLControlFM(int ch, int op, int value) { int inch = toInternalFMChannel(ch); FMEnvelopeParameter param = getParameterTL(op); writeFMEnveropeParameterToRegister(inch, param, value); isTLCtrlFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } void OPNAController::setMLControlFM(int ch, int op, int value) { int inch = toInternalFMChannel(ch); FMEnvelopeParameter param = getParameterML(op); writeFMEnveropeParameterToRegister(inch, param, value); isMLCtrlFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } void OPNAController::setARControlFM(int ch, int op, int value) { int inch = toInternalFMChannel(ch); FMEnvelopeParameter param = getParameterAR(op); writeFMEnveropeParameterToRegister(inch, param, value); isARCtrlFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } void OPNAController::setDRControlFM(int ch, int op, int value) { int inch = toInternalFMChannel(ch); FMEnvelopeParameter param = getParameterDR(op); writeFMEnveropeParameterToRegister(inch, param, value); isDRCtrlFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } void OPNAController::setRRControlFM(int ch, int op, int value) { int inch = toInternalFMChannel(ch); FMEnvelopeParameter param = getParameterRR(op); writeFMEnveropeParameterToRegister(inch, param, value); isRRCtrlFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } void OPNAController::setBrightnessFM(int ch, int value) { int inch = toInternalFMChannel(ch); std::vector ops = getOperatorsInLevel(1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL)); for (auto& op : ops) { FMEnvelopeParameter param = getParameterTL(op); int v = envFM_[inch]->getParameterValue(param) + value; if (v < 0) v = 0; else if (v > 127) v = 127; writeFMEnveropeParameterToRegister(inch, param, v); isBrightFM_[inch][op] = true; opSeqItFM_[inch].at(param).reset(); } } /********** For state retrieve **********/ void OPNAController::haltSequencesFM(int ch) { int inch = toInternalFMChannel(ch); for (auto& p : getFMEnvelopeParametersForOperator(toChannelOperatorType(ch))) { if (auto& it = opSeqItFM_[inch].at(p)) it->end(); } if (treItFM_[ch]) treItFM_[ch]->end(); if (arpItFM_[ch]) arpItFM_[ch]->end(); if (ptItFM_[ch]) ptItFM_[ch]->end(); if (vibItFM_[ch]) vibItFM_[ch]->next(); if (nsItFM_[ch]) nsItFM_[ch]->end(); } /********** Mute **********/ void OPNAController::setMuteFMState(int ch, bool isMute) { isMuteFM_[ch] = isMute; if (isMute) { resetFMChannelEnvelope(ch); } else { int inch = toInternalFMChannel(ch); switch (toChannelOperatorType(ch)) { case FMOperatorType::All: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR1)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR2, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR2)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR3, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR3)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR4, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR4)); break; case FMOperatorType::Op1: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR1)); break; case FMOperatorType::Op2: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR2, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR2)); break; case FMOperatorType::Op3: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR3, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR3)); break; case FMOperatorType::Op4: writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR4, envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR4)); break; } } } bool OPNAController::isMuteFM(int ch) { return isMuteFM_[ch]; } /********** Chip details **********/ bool OPNAController::isKeyOnFM(int ch) const { return isKeyOnFM_[ch]; } bool OPNAController::isTonePortamentoFM(int ch) const { return isTonePrtmFM_[ch]; } bool OPNAController::enableFMEnvelopeReset(int ch) const { return envFM_[toInternalFMChannel(ch)] ? enableEnvResetFM_[ch] : true; } ToneDetail OPNAController::getFMTone(int ch) const { return baseToneFM_[ch].front(); } /***********************************/ void OPNAController::initFM() { lfoFreq_ = -1; uint8_t mode = 0; switch (mode_) { case SongType::Standard: mode = 0; break; case SongType::FM3chExpanded: mode = 0x40; break; } opna_->setRegister(0x27, mode); for (int inch = 0; inch < 6; ++inch) { // Init envelope envFM_[inch] = std::make_unique(-1); refInstFM_[inch].reset(); // Init pan uint32_t bch = getFMChannelOffset(inch); panFM_[inch] = 3; opna_->setRegister(0xb4 + bch, 0xc0); // Init sequence for (auto& p : opSeqItFM_[inch]) { p.second.reset(); } lfoStartCntFM_[inch] = -1; isFBCtrlFM_[inch] = false; for (int op = 0; op < 4; ++op) { isTLCtrlFM_[inch][op] = false; isMLCtrlFM_[inch][op] = false; isARCtrlFM_[inch][op] = false; isDRCtrlFM_[inch][op] = false; isRRCtrlFM_[inch][op] = false; isBrightFM_[inch][op] = false; } } size_t fmch = getFMChannelCount(mode_); for (size_t ch = 0; ch < fmch; ++ch) { // Init operators key off isKeyOnFM_[ch] = false; // Init echo buffer baseToneFM_[ch] = std::deque(4); for (auto& td : baseToneFM_[ch]) { td.octave = -1; } keyToneFM_[ch].note = Note::C; // Dummy keyToneFM_[ch].octave = -1; keyToneFM_[ch].pitch = 0; // Dummy sumPitchFM_[ch] = 0; baseVolFM_[ch] = 0; // Init volume tmpVolFM_[ch] = -1; enableEnvResetFM_[ch] = false; hasResetEnvFM_[ch] = false; // Init sequence hasPreSetTickEventFM_[ch] = false; arpItFM_[ch].reset(); ptItFM_[ch].reset(); needToneSetFM_[ch] = false; // Effect isArpEffFM_[ch] = false; prtmFM_[ch] = 0; isTonePrtmFM_[ch] = false; vibItFM_[ch].reset(); treItFM_[ch].reset(); volSldFM_[ch] = 0; sumVolSldFM_[ch] = 0; detuneFM_[ch] = 0; nsItFM_[ch].reset(); sumNoteSldFM_[ch] = 0; noteSldFMSetFlag_[ch] = false; transposeFM_[ch] = 0; } } int OPNAController::toInternalFMChannel(int ch) const { if (0 <= ch && ch < 6) return ch; else if (mode_ == SongType::FM3chExpanded && 6 <= ch && ch < 9) return 2; else throw std::out_of_range("Out of channel range."); } uint8_t OPNAController::getFMKeyOnOffChannelMask(int ch) const { switch (toInternalFMChannel(ch)) { case 0: return 0x00; case 1: return 0x01; case 2: return 0x02; case 3: return 0x04; case 4: return 0x05; case 5: return 0x06; default: return 0; } } uint32_t OPNAController::getFMChannelOffset(int ch, bool forPitch) const { if (mode_ == SongType::FM3chExpanded && forPitch) { switch (ch) { case 0: case 1: return static_cast(ch); case 3: case 4: case 5: return static_cast(0x100 + ch % 3); case 2: // FM3-OP1 return 9; case 6: // FM3-OP2 return 10; case 7: // FM3-OP3 return 8; case 8: // FM3-OP4 return 2; default: return 0; } } else { switch (toInternalFMChannel(ch)) { case 0: case 1: case 2: return static_cast(ch); case 3: case 4: case 5: return static_cast(0x100 + ch % 3); default: return 0; } } } FMOperatorType OPNAController::toChannelOperatorType(int ch) const { FMOperatorType opType; if (mode_ == SongType::FM3chExpanded && toInternalFMChannel(ch) == 2) { switch (ch) { case 2: opType = FMOperatorType::Op1; break; case 6: opType = FMOperatorType::Op2; break; case 7: opType = FMOperatorType::Op3; break; case 8: opType = FMOperatorType::Op4; break; default: throw std::out_of_range("out of range."); } } else { opType = FMOperatorType::All; } return opType; } std::vector OPNAController::getFMEnvelopeParametersForOperator(FMOperatorType op) const { std::vector params; switch (op) { case FMOperatorType::All: params = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1, FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2, FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3, FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; break; case FMOperatorType::Op1: params = { FMEnvelopeParameter::AL, FMEnvelopeParameter::FB, FMEnvelopeParameter::AR1, FMEnvelopeParameter::DR1, FMEnvelopeParameter::SR1, FMEnvelopeParameter::RR1, FMEnvelopeParameter::SL1, FMEnvelopeParameter::TL1, FMEnvelopeParameter::KS1, FMEnvelopeParameter::ML1, FMEnvelopeParameter::DT1 }; break; case FMOperatorType::Op2: params = { FMEnvelopeParameter::AR2, FMEnvelopeParameter::DR2, FMEnvelopeParameter::SR2, FMEnvelopeParameter::RR2, FMEnvelopeParameter::SL2, FMEnvelopeParameter::TL2, FMEnvelopeParameter::KS2, FMEnvelopeParameter::ML2, FMEnvelopeParameter::DT2 }; break; case FMOperatorType::Op3: params = { FMEnvelopeParameter::AR3, FMEnvelopeParameter::DR3, FMEnvelopeParameter::SR3, FMEnvelopeParameter::RR3, FMEnvelopeParameter::SL3, FMEnvelopeParameter::TL3, FMEnvelopeParameter::KS3, FMEnvelopeParameter::ML3, FMEnvelopeParameter::DT3 }; break; case FMOperatorType::Op4: params = { FMEnvelopeParameter::AR4, FMEnvelopeParameter::DR4, FMEnvelopeParameter::SR4, FMEnvelopeParameter::RR4, FMEnvelopeParameter::SL4, FMEnvelopeParameter::TL4, FMEnvelopeParameter::KS4, FMEnvelopeParameter::ML4, FMEnvelopeParameter::DT4 }; break; } return params; } void OPNAController::writeFMEnvelopeToRegistersFromInstrument(int inch) { uint32_t bch = getFMChannelOffset(inch); // Bank and channel offset uint8_t data1, data2; int al; data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::FB)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::FB, data1); data1 <<= 3; al = refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::AL); envFM_[inch]->setParameterValue(FMEnvelopeParameter::AL, al); data1 += al; opna_->setRegister(0xb0 + bch, data1); uint32_t offset = bch; // Operator 1 data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DT1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DT1, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::ML1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::ML1, data2); data1 |= data2; opna_->setRegister(0x30 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL1)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) data1 = calculateTL(2, data1); else if (isCarrier(0, al)) data1 = calculateTL(inch, data1); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL1, data1); opna_->setRegister(0x40 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::KS1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::KS1, data1); data1 <<= 6; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::AR1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::AR1, data2); data1 |= data2; opna_->setRegister(0x50 + offset, data1); data1 = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM1)) : 0; data1 <<= 7; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DR1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DR1, data2); data1 |= data2; opna_->setRegister(0x60 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SR1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SR1, data2); opna_->setRegister(0x70 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SL1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SL1, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR1)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::RR1, data2); data1 |= data2; opna_->setRegister(0x80 + offset, data1); int tmp = refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SSGEG1); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SSGEG1, tmp); data1 = judgeSSEGRegisterValue(tmp); opna_->setRegister(0x90 + offset, data1); offset = bch + 8; // Operator 2 data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DT2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DT2, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::ML2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::ML2, data2); data1 |= data2; opna_->setRegister(0x30 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL2)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) data1 = calculateTL(6, data1); else if (isCarrier(1, al)) data1 = calculateTL(inch, data1); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL2, data1); opna_->setRegister(0x40 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::KS2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::KS2, data1); data1 <<= 6; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::AR2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::AR2, data2); data1 |= data2; opna_->setRegister(0x50 + offset, data1); data1 = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM2)) : 0; data1 <<= 7; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DR2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DR2, data2); data1 |= data2; opna_->setRegister(0x60 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SR2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SR2, data2); opna_->setRegister(0x70 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SL2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SL2, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR2)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::RR2, data2); data1 |= data2; opna_->setRegister(0x80 + offset, data1); tmp = refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SSGEG2); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SSGEG2, tmp); data1 = judgeSSEGRegisterValue(tmp); opna_->setRegister(0x90 + offset, data1); offset = bch + 4; // Operator 3 data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DT3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DT3, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::ML3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::ML3, data2); data1 |= data2; opna_->setRegister(0x30 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL3)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) data1 = calculateTL(7, data1); else if (isCarrier(2, al)) data1 = calculateTL(inch, data1); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL3, data1); opna_->setRegister(0x40 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::KS3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::KS3, data1); data1 <<= 6; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::AR3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::AR3, data2); data1 |= data2; opna_->setRegister(0x50 + offset, data1); data1 = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM3)) : 0; data1 <<= 7; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DR3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DR3, data2); data1 |= data2; opna_->setRegister(0x60 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SR3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SR3, data2); opna_->setRegister(0x70 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SL3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SL3, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR3)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::RR3, data2); data1 |= data2; opna_->setRegister(0x80 + offset, data1); tmp = refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SSGEG3); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SSGEG3, tmp); data1 = judgeSSEGRegisterValue(tmp); opna_->setRegister(0x90 + offset, data1); offset = bch + 12; // Operator 4 data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DT4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DT4, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::ML4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::ML4, data2); data1 |= data2; opna_->setRegister(0x30 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::TL4)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) data1 = calculateTL(8, data1); else data1 = calculateTL(inch, data1); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL4, data1); opna_->setRegister(0x40 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::KS4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::KS4, data1); data1 <<= 6; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::AR4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::AR4, data2); data1 |= data2; opna_->setRegister(0x50 + offset, data1); data1 = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM4)) : 0; data1 <<= 7; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::DR4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::DR4, data2); data1 |= data2; opna_->setRegister(0x60 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SR4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SR4, data2); opna_->setRegister(0x70 + offset, data1); data1 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SL4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SL4, data1); data1 <<= 4; data2 = static_cast(refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR4)); envFM_[inch]->setParameterValue(FMEnvelopeParameter::RR4, data2); data1 |= data2; opna_->setRegister(0x80 + offset, data1); tmp = refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::SSGEG4); envFM_[inch]->setParameterValue(FMEnvelopeParameter::SSGEG4, tmp); data1 = judgeSSEGRegisterValue(tmp); opna_->setRegister(0x90 + offset, data1); } void OPNAController::writeFMEnveropeParameterToRegister(int inch, FMEnvelopeParameter param, int value) { uint32_t bch = getFMChannelOffset(inch); // Bank and channel offset uint8_t data; int tmp; envFM_[inch]->setParameterValue(param, value); switch (param) { case FMEnvelopeParameter::AL: case FMEnvelopeParameter::FB: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::FB) << 3); data += envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL); opna_->setRegister(0xb0 + bch, data); break; case FMEnvelopeParameter::DT1: case FMEnvelopeParameter::ML1: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DT1) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::ML1); opna_->setRegister(0x30 + bch, data); break; case FMEnvelopeParameter::TL1: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL1)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(2, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL1, data); // Update } else if (isCarrier(0, envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL))) { data = calculateTL(inch, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL1, data); // Update } opna_->setRegister(0x40 + bch, data); break; case FMEnvelopeParameter::KS1: case FMEnvelopeParameter::AR1: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::KS1) << 6); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::AR1); opna_->setRegister(0x50 + bch, data); break; case FMEnvelopeParameter::DR1: data = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM1)) : 0; data <<= 7; data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR1); opna_->setRegister(0x60 + bch, data); break; case FMEnvelopeParameter::SR1: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SR1)); opna_->setRegister(0x70 + bch, data); break; case FMEnvelopeParameter::SL1: case FMEnvelopeParameter::RR1: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SL1) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR1); opna_->setRegister(0x80 + bch, data); break; case::FMEnvelopeParameter::SSGEG1: tmp = envFM_[inch]->getParameterValue(FMEnvelopeParameter::SSGEG1); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch, data); break; case FMEnvelopeParameter::DT2: case FMEnvelopeParameter::ML2: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DT2) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::ML2); opna_->setRegister(0x30 + bch + 8, data); break; case FMEnvelopeParameter::TL2: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL2)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(6, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL2, data); // Update } else if (isCarrier(1, envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL))) { data = calculateTL(inch, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL2, data); // Update } opna_->setRegister(0x40 + bch + 8, data); break; case FMEnvelopeParameter::KS2: case FMEnvelopeParameter::AR2: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::KS2) << 6); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::AR2); opna_->setRegister(0x50 + bch + 8, data); break; case FMEnvelopeParameter::DR2: data = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM2)) : 0; data <<= 7; data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR2); opna_->setRegister(0x60 + bch + 8, data); break; case FMEnvelopeParameter::SR2: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SR2)); opna_->setRegister(0x70 + bch + 8, data); break; case FMEnvelopeParameter::SL2: case FMEnvelopeParameter::RR2: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SL2) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR2); opna_->setRegister(0x80 + bch + 8, data); break; case FMEnvelopeParameter::SSGEG2: tmp = envFM_[inch]->getParameterValue(FMEnvelopeParameter::SSGEG2); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch + 8, data); break; case FMEnvelopeParameter::DT3: case FMEnvelopeParameter::ML3: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DT3) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::ML3); opna_->setRegister(0x30 + bch + 4, data); break; case FMEnvelopeParameter::TL3: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL3)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(7, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL3, data); // Update } else if (isCarrier(2, envFM_[inch]->getParameterValue(FMEnvelopeParameter::AL))) { data = calculateTL(inch, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL3, data); // Update } opna_->setRegister(0x40 + bch + 4, data); break; case FMEnvelopeParameter::KS3: case FMEnvelopeParameter::AR3: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::KS3) << 6); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::AR3); opna_->setRegister(0x50 + bch + 4, data); break; case FMEnvelopeParameter::DR3: data = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM3)) : 0; data <<= 7; data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR3); opna_->setRegister(0x60 + bch + 4, data); break; case FMEnvelopeParameter::SR3: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SR3)); opna_->setRegister(0x70 + bch + 4, data); break; case FMEnvelopeParameter::SL3: case FMEnvelopeParameter::RR3: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SL3) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR3); opna_->setRegister(0x80 + bch + 4, data); break; case FMEnvelopeParameter::SSGEG3: tmp = envFM_[inch]->getParameterValue(FMEnvelopeParameter::SSGEG3); data = (tmp == -1) ? 0 : static_cast(0x08 + tmp); opna_->setRegister(0x90 + bch + 4, data); break; case FMEnvelopeParameter::DT4: case FMEnvelopeParameter::ML4: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DT4) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::ML4); opna_->setRegister(0x30 + bch + 12, data); break; case FMEnvelopeParameter::TL4: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL4)); // Adjust volume if (mode_ == SongType::FM3chExpanded && inch == 2) { data = calculateTL(8, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL4, data); // Update } else { data = calculateTL(inch, data); envFM_[inch]->setParameterValue(FMEnvelopeParameter::TL4, data); // Update } opna_->setRegister(0x40 + bch + 12, data); break; case FMEnvelopeParameter::KS4: case FMEnvelopeParameter::AR4: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::KS4) << 6); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::AR4); opna_->setRegister(0x50 + bch + 12, data); break; case FMEnvelopeParameter::DR4: data = refInstFM_[inch]->getLFOEnabled() ? static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM4)) : 0; data <<= 7; data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR4); opna_->setRegister(0x60 + bch + 12, data); break; case FMEnvelopeParameter::SR4: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SR4)); opna_->setRegister(0x70 + bch + 12, data); break; case FMEnvelopeParameter::SL4: case FMEnvelopeParameter::RR4: data = static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::SL4) << 4); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::RR4); opna_->setRegister(0x80 + bch + 12, data); break; case FMEnvelopeParameter::SSGEG4: tmp = envFM_[inch]->getParameterValue(FMEnvelopeParameter::SSGEG4); data = judgeSSEGRegisterValue(tmp); opna_->setRegister(0x90 + bch + 12, data); break; } } void OPNAController::restoreFMEnvelopeFromReset(int ch) { int inch = toInternalFMChannel(ch); if (hasResetEnvFM_[ch] == false || !refInstFM_[inch]) return; switch (mode_) { case SongType::Standard: { if (refInstFM_[inch]->getEnvelopeResetEnabled(FMOperatorType::All)) { hasResetEnvFM_[inch] = false; writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR1, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR1)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR2, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR2)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR3, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR3)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR4, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR4)); } break; } case SongType::FM3chExpanded: { if (refInstFM_[inch]->getEnvelopeResetEnabled(toChannelOperatorType(ch))) { if (inch == 2) { FMEnvelopeParameter param; switch (ch) { case 2: param = FMEnvelopeParameter::RR1; break; case 6: param = FMEnvelopeParameter::RR2; break; case 7: param = FMEnvelopeParameter::RR3; break; case 8: param = FMEnvelopeParameter::RR4; break; default: throw std::out_of_range("out of range."); } hasResetEnvFM_[ch] = false; writeFMEnveropeParameterToRegister(2, param, refInstFM_[2]->getEnvelopeParameter(param)); } else { hasResetEnvFM_[inch] = false; writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR1, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR1)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR2, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR2)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR3, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR3)); writeFMEnveropeParameterToRegister(inch, FMEnvelopeParameter::RR4, refInstFM_[inch]->getEnvelopeParameter(FMEnvelopeParameter::RR4)); } } break; } } } void OPNAController::writeFMLFOAllRegisters(int inch) { if (!refInstFM_[inch]->getLFOEnabled() || lfoStartCntFM_[inch] > 0) { // Clear data uint32_t bch = getFMChannelOffset(inch); // Bank and channel offset opna_->setRegister(0xb4 + bch, static_cast(panFM_[inch] << 6)); opna_->setRegister(0x60 + bch, static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR1))); opna_->setRegister(0x60 + bch + 8, static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR2))); opna_->setRegister(0x60 + bch + 4, static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR3))); opna_->setRegister(0x60 + bch + 12, static_cast(envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR4))); } else { writeFMLFORegister(inch, FMLFOParameter::FREQ); writeFMLFORegister(inch, FMLFOParameter::PMS); writeFMLFORegister(inch, FMLFOParameter::AMS); writeFMLFORegister(inch, FMLFOParameter::AM1); writeFMLFORegister(inch, FMLFOParameter::AM2); writeFMLFORegister(inch, FMLFOParameter::AM3); writeFMLFORegister(inch, FMLFOParameter::AM4); lfoStartCntFM_[inch] = -1; } } void OPNAController::writeFMLFORegister(int inch, FMLFOParameter param) { uint32_t bch = getFMChannelOffset(inch); // Bank and channel offset uint8_t data; switch (param) { case FMLFOParameter::FREQ: lfoFreq_ = refInstFM_[inch]->getLFOParameter(FMLFOParameter::FREQ); opna_->setRegister(0x22, static_cast(lfoFreq_ | (1 << 3))); break; case FMLFOParameter::PMS: case FMLFOParameter::AMS: data = static_cast(panFM_[inch] << 6); data |= (refInstFM_[inch]->getLFOParameter(FMLFOParameter::AMS) << 4); data |= refInstFM_[inch]->getLFOParameter(FMLFOParameter::PMS); opna_->setRegister(0xb4 + bch, data); break; case FMLFOParameter::AM1: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM1) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR1); opna_->setRegister(0x60 + bch, data); break; case FMLFOParameter::AM2: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM2) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR2); opna_->setRegister(0x60 + bch + 8, data); break; case FMLFOParameter::AM3: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM3) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR3); opna_->setRegister(0x60 + bch + 4, data); break; case FMLFOParameter::AM4: data = static_cast(refInstFM_[inch]->getLFOParameter(FMLFOParameter::AM4) << 7); data |= envFM_[inch]->getParameterValue(FMEnvelopeParameter::DR4); opna_->setRegister(0x60 + bch + 12, data); break; default: break; } } void OPNAController::checkLFOUsed() { for (int inch = 0; inch < 6; ++inch) { if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) return; } if (lfoFreq_ != -1) { lfoFreq_ = -1; opna_->setRegister(0x22, 0); // LFO off } } void OPNAController::setFrontFMSequences(int ch) { if (isMuteFM(ch)) return; int inch = toInternalFMChannel(ch); if (refInstFM_[inch] && refInstFM_[inch]->getLFOEnabled()) { lfoStartCntFM_[inch] = refInstFM_[inch]->getLFOParameter(FMLFOParameter::Count); writeFMLFOAllRegisters(inch); } else { lfoStartCntFM_[inch] = -1; } checkOperatorSequenceFM(ch, 1); if (treItFM_[ch]) treItFM_[ch]->front(); sumVolSldFM_[ch] += volSldFM_[ch]; checkVolumeEffectFM(ch); if (arpItFM_[ch]) checkRealToneFMByArpeggio(ch, arpItFM_[ch]->front()); checkPortamentoFM(ch); if (ptItFM_[ch]) checkRealToneFMByPitch(ch, ptItFM_[ch]->front()); if (vibItFM_[ch]) { vibItFM_[ch]->front(); needToneSetFM_[ch] = true; } if (nsItFM_[ch] && nsItFM_[ch]->front() != -1) { sumNoteSldFM_[ch] += nsItFM_[ch]->getCommandType(); needToneSetFM_[ch] = true; } writePitchFM(ch); } void OPNAController::releaseStartFMSequences(int ch) { if (isMuteFM(ch)) return; int inch = toInternalFMChannel(ch); if (lfoStartCntFM_[inch] > 0) { --lfoStartCntFM_[inch]; writeFMLFOAllRegisters(inch); } checkOperatorSequenceFM(ch, 2); if (treItFM_[ch]) treItFM_[ch]->next(true); sumVolSldFM_[ch] += volSldFM_[ch]; checkVolumeEffectFM(ch); if (arpItFM_[ch]) checkRealToneFMByArpeggio(ch, arpItFM_[ch]->next(true)); checkPortamentoFM(ch); if (ptItFM_[ch]) checkRealToneFMByPitch(ch, ptItFM_[ch]->next(true)); if (vibItFM_[ch]) { vibItFM_[ch]->next(true); needToneSetFM_[ch] = true; } if (nsItFM_[ch] && nsItFM_[ch]->next(true) != -1) { sumNoteSldFM_[ch] += nsItFM_[ch]->getCommandType(); needToneSetFM_[ch] = true; } if (needToneSetFM_[ch]) writePitchFM(ch); } void OPNAController::tickEventFM(int ch) { if (hasPreSetTickEventFM_[ch]) { hasPreSetTickEventFM_[ch] = false; } else { if (isMuteFM(ch)) return; int inch = toInternalFMChannel(ch); if (lfoStartCntFM_[inch] > 0) { --lfoStartCntFM_[inch]; writeFMLFOAllRegisters(inch); } checkOperatorSequenceFM(ch, 0); if (treItFM_[ch]) treItFM_[ch]->next(); sumVolSldFM_[ch] += volSldFM_[ch]; checkVolumeEffectFM(ch); if (arpItFM_[ch]) checkRealToneFMByArpeggio(ch, arpItFM_[ch]->next()); checkPortamentoFM(ch); if (ptItFM_[ch]) checkRealToneFMByPitch(ch, ptItFM_[ch]->next()); if (vibItFM_[ch]) { vibItFM_[ch]->next(); needToneSetFM_[ch] = true; } if (nsItFM_[ch] && nsItFM_[ch]->next() != -1) { sumNoteSldFM_[ch] += nsItFM_[ch]->getCommandType(); needToneSetFM_[ch] = true; } if (needToneSetFM_[ch]) writePitchFM(ch); } } void OPNAController::checkOperatorSequenceFM(int ch, int type) { int inch = toInternalFMChannel(ch); for (auto& p : getFMEnvelopeParametersForOperator(toChannelOperatorType(ch))) { if (auto& it = opSeqItFM_[inch].at(p)) { int t; switch (type) { case 0: t = it->next(); break; case 1: t = it->front(); break; case 2: t = it->next(true); break; default: throw std::out_of_range("The range of type is 0-2."); } if (t != -1) { int d = it->getCommandType(); if (d != envFM_[inch]->getParameterValue(p)) { writeFMEnveropeParameterToRegister(inch, p, d); } } } } } void OPNAController::checkVolumeEffectFM(int ch) { int v; if (treItFM_[ch]) { v = treItFM_[ch]->getCommandType() + sumVolSldFM_[ch]; } else { if (volSldFM_[ch]) v = sumVolSldFM_[ch]; else return; } uint32_t bch = getFMChannelOffset(ch); int inch = toInternalFMChannel(ch); switch (toChannelOperatorType(ch)) { case FMOperatorType::All: { int al = envFM_[ch]->getParameterValue(FMEnvelopeParameter::AL); if (isCarrier(0, al)) { // Operator 1 int data = envFM_[ch]->getParameterValue(FMEnvelopeParameter::TL1) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch, static_cast(data)); } if (isCarrier(1, al)) { // Operator 2 int data = envFM_[ch]->getParameterValue(FMEnvelopeParameter::TL2) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 8, static_cast(data)); } if (isCarrier(2, al)) { // Operator 3 int data = envFM_[ch]->getParameterValue(FMEnvelopeParameter::TL3) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 4, static_cast(data)); } if (isCarrier(3, al)) { // Operator 4 int data = envFM_[ch]->getParameterValue(FMEnvelopeParameter::TL4) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 12, static_cast(data)); } break; } case FMOperatorType::Op1: { int data = envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL1) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch, static_cast(data)); break; } case FMOperatorType::Op2: { int data = envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL2) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 8, static_cast(data)); break; } case FMOperatorType::Op3: { int data = envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL3) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 4, static_cast(data)); break; } case FMOperatorType::Op4: { int data = envFM_[inch]->getParameterValue(FMEnvelopeParameter::TL4) + v; if (data > 127) data = 127; else if (data < 0) data = 0; opna_->setRegister(0x40 + bch + 12, static_cast(data)); break; } } } void OPNAController::checkRealToneFMByArpeggio(int ch, int seqPos) { if (seqPos == -1) return; int type = arpItFM_[ch]->getCommandType(); if (type == -1) return; switch (arpItFM_[ch]->getSequenceType()) { case SequenceType::ABSOLUTE_SEQUENCE: { std::pair pair = noteNumberToOctaveAndNote( octaveAndNoteToNoteNumber(baseToneFM_[ch].front().octave, baseToneFM_[ch].front().note) + type - 48); keyToneFM_[ch].octave = pair.first; keyToneFM_[ch].note = pair.second; break; } case SequenceType::FIXED_SEQUENCE: { std::pair pair = noteNumberToOctaveAndNote(type); keyToneFM_[ch].octave = pair.first; keyToneFM_[ch].note = pair.second; break; } case SequenceType::RELATIVE_SEQUENCE: // Relative { std::pair pair = noteNumberToOctaveAndNote( octaveAndNoteToNoteNumber(keyToneFM_[ch].octave, keyToneFM_[ch].note) + type - 48); keyToneFM_[ch].octave = pair.first; keyToneFM_[ch].note = pair.second; break; } default: break; } needToneSetFM_[ch] = true; } void OPNAController::checkPortamentoFM(int ch) { if ((!arpItFM_[ch] || arpItFM_[ch]->getPosition() == -1) && prtmFM_[ch]) { if (isTonePrtmFM_[ch]) { int dif = ( octaveAndNoteToNoteNumber(baseToneFM_[ch].front().octave, baseToneFM_[ch].front().note) * 32 + baseToneFM_[ch].front().pitch ) - ( octaveAndNoteToNoteNumber(keyToneFM_[ch].octave, keyToneFM_[ch].note) * 32 + keyToneFM_[ch].pitch ); if (dif > 0) { if (dif - prtmFM_[ch] < 0) { keyToneFM_[ch] = baseToneFM_[ch].front(); } else { keyToneFM_[ch].pitch += prtmFM_[ch]; } needToneSetFM_[ch] = true; } else if (dif < 0) { if (dif + prtmFM_[ch] > 0) { keyToneFM_[ch] = baseToneFM_[ch].front(); } else { keyToneFM_[ch].pitch -= prtmFM_[ch]; } needToneSetFM_[ch] = true; } } else { keyToneFM_[ch].pitch += prtmFM_[ch]; needToneSetFM_[ch] = true; } } } void OPNAController::checkRealToneFMByPitch(int ch, int seqPos) { if (seqPos == -1) return; int diff = ptItFM_[ch]->getCommandType() - 127; if (diff < -127) return; // Skip if command type == -1 switch (ptItFM_[ch]->getSequenceType()) { case SequenceType::ABSOLUTE_SEQUENCE: sumPitchFM_[ch] = diff; break; case SequenceType::RELATIVE_SEQUENCE: sumPitchFM_[ch] += diff; break; default: break; } needToneSetFM_[ch] = true; } void OPNAController::writePitchFM(int ch) { if (keyToneFM_[ch].octave == -1) return; // Not set note yet uint16_t p = PitchConverter::getPitchFM( keyToneFM_[ch].note, keyToneFM_[ch].octave, keyToneFM_[ch].pitch + sumPitchFM_[ch] + (vibItFM_[ch] ? vibItFM_[ch]->getCommandType() : 0) + detuneFM_[ch] + sumNoteSldFM_[ch] + transposeFM_[ch]); uint32_t offset = getFMChannelOffset(ch, true); opna_->setRegister(0xa4 + offset, p >> 8); opna_->setRegister(0xa0 + offset, p & 0x00ff); needToneSetFM_[ch] = false; } void OPNAController::setInstrumentFMProperties(int ch) { int inch = toInternalFMChannel(ch); FMOperatorType opType = toChannelOperatorType(ch); enableEnvResetFM_[ch] = refInstFM_[inch]->getEnvelopeResetEnabled(opType); } bool OPNAController::isCarrier(int op, int al) { switch (op) { case 0: return (al == 7); case 1: switch (al) { case 4: case 5: case 6: case 7: return true; default: return false; } case 2: switch (al) { case 5: case 6: case 7: return true; default: return false; } case 3: return true; default: throw std::invalid_argument("Invalid operator."); } } std::vector OPNAController::getOperatorsInLevel(int level, int al) { switch (level) { case 0: switch (al) { case 0: case 1: case 2: case 3: return { 3 }; case 4: return { 1, 3 }; case 5: case 6: return { 1, 2, 3 }; case 7: return { 0, 1, 2, 3 }; default: throw std::invalid_argument("Invalid algorithm."); } case 1: switch (al) { case 0: case 1: return { 2 }; case 2: return { 0, 2 }; case 3: return { 1, 2 }; case 4: return { 0, 2 }; case 5: case 6: return { 0 }; case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } case 2: switch (al) { case 0: return { 1 }; case 1: return { 0, 1 }; case 2: return { 1 }; case 3: return { 0 }; case 4: case 5: case 6: case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } case 3: switch (al) { case 0: return { 0 }; case 1: case 2: case 3: case 4: case 5: case 6: case 7: return {}; default: throw std::invalid_argument("Invalid algorithm."); } default: throw std::invalid_argument("Invalid operator level."); } } //---------- SSG ----------// /********** Key on-off **********/ void OPNAController::keyOnSSG(int ch, Note note, int octave, int pitch, bool isJam) { if (isMuteSSG(ch)) return; updateEchoBufferSSG(ch, octave, note, pitch); if (isTonePortamentoSSG(ch)) { keyToneSSG_[ch].pitch += (sumNoteSldSSG_[ch] +transposeSSG_[ch]); } else { keyToneSSG_[ch] = baseToneSSG_[ch].front(); sumPitchSSG_[ch] = 0; sumVolSldSSG_[ch] = 0; } if (tmpVolSSG_[ch] != -1 && !volSldSSG_[ch]) { setVolumeSSG(ch, baseVolSSG_[ch]); } if (!noteSldSSGSetFlag_) { nsItSSG_[ch].reset(); } noteSldSSGSetFlag_ = false; needToneSetSSG_[ch] = true; sumNoteSldSSG_[ch] = 0; transposeSSG_[ch] = 0; isKeyOnSSG_[ch] = false; // For first tick check setFrontSSGSequences(ch); hasPreSetTickEventSSG_[ch] = isJam; isKeyOnSSG_[ch] = true; } void OPNAController::keyOnSSG(int ch, int echoBuf) { ToneDetail& td = baseToneSSG_[ch].at(static_cast(echoBuf)); if (td.octave == -1) return; keyOnSSG(ch, td.note, td.octave, td.pitch); } void OPNAController::keyOffSSG(int ch, bool isJam) { if (!isKeyOnSSG_[ch]) { tickEventSSG(ch); return; } releaseStartSSGSequences(ch); hasPreSetTickEventSSG_[ch] = isJam; isKeyOnSSG_[ch] = false; } void OPNAController::updateEchoBufferSSG(int ch, int octave, Note note, int pitch) { baseToneSSG_[ch].pop_back(); baseToneSSG_[ch].push_front({ octave, note, pitch }); } /********** Set instrument **********/ /// NOTE: inst != nullptr void OPNAController::setInstrumentSSG(int ch, std::shared_ptr inst) { refInstSSG_[ch] = inst; if (inst->getWaveFormEnabled()) wfItSSG_[ch] = inst->getWaveFormSequenceIterator(); else wfItSSG_[ch].reset(); if (inst->getToneNoiseEnabled()) tnItSSG_[ch] = inst->getToneNoiseSequenceIterator(); else tnItSSG_[ch].reset(); if (inst->getEnvelopeEnabled()) envItSSG_[ch] = inst->getEnvelopeSequenceIterator(); else envItSSG_[ch].reset(); if (!isArpEffSSG_[ch]) { if (inst->getArpeggioEnabled()) arpItSSG_[ch] = inst->getArpeggioSequenceIterator(); else arpItSSG_[ch].reset(); } if (inst->getPitchEnabled()) ptItSSG_[ch] = inst->getPitchSequenceIterator(); else ptItSSG_[ch].reset(); } void OPNAController::updateInstrumentSSG(int instNum) { for (int ch = 0; ch < 3; ++ch) { if (refInstSSG_[ch] && refInstSSG_[ch]->isRegisteredWithManager() && refInstSSG_[ch]->getNumber() == instNum) { if (!refInstSSG_[ch]->getWaveFormEnabled()) wfItSSG_[ch].reset(); if (!refInstSSG_[ch]->getToneNoiseEnabled()) tnItSSG_[ch].reset(); if (!refInstSSG_[ch]->getEnvelopeEnabled()) envItSSG_[ch].reset(); if (!refInstSSG_[ch]->getArpeggioEnabled()) arpItSSG_[ch].reset(); if (!refInstSSG_[ch]->getPitchEnabled()) ptItSSG_[ch].reset(); } } } /********** Set volume **********/ void OPNAController::setVolumeSSG(int ch, int volume) { if (volume > 0xf) return; // Out of range baseVolSSG_[ch] = volume; tmpVolSSG_[ch] = -1; if (isKeyOnSSG(ch)) setRealVolumeSSG(ch); } void OPNAController::setTemporaryVolumeSSG(int ch, int volume) { if (volume > 0xf) return; // Out of range tmpVolSSG_[ch] = volume; if (isKeyOnSSG(ch)) setRealVolumeSSG(ch); } void OPNAController::setRealVolumeSSG(int ch) { if (isBuzzEffSSG_[ch] || isHardEnvSSG_[ch]) return; int volume = (tmpVolSSG_[ch] == -1) ? baseVolSSG_[ch] : tmpVolSSG_[ch]; if (envItSSG_[ch]) { int type = envItSSG_[ch]->getCommandType(); if (0 <= type && type < 16) { volume = volume - (15 - type); if (volume < 0) volume = 0; } } if (treItSSG_[ch]) volume += treItSSG_[ch]->getCommandType(); volume += sumVolSldSSG_[ch]; if (volume > 15) volume = 15; else if (volume < 0) volume = 0; opna_->setRegister(0x08 + static_cast(ch), static_cast(volume)); needEnvSetSSG_[ch] = false; } void OPNAController::setMasterVolumeSSG(double dB) { opna_->setVolumeSSG(dB); } /********** Set effect **********/ void OPNAController::setArpeggioEffectSSG(int ch, int second, int third) { if (second || third) { arpItSSG_[ch] = std::make_unique(second, third); isArpEffSSG_[ch] = true; } else { if (!refInstSSG_[ch] || !refInstSSG_[ch]->getArpeggioEnabled()) arpItSSG_[ch].reset(); else arpItSSG_[ch] = refInstSSG_[ch]->getArpeggioSequenceIterator(); isArpEffSSG_[ch] = false; } } void OPNAController::setPortamentoEffectSSG(int ch, int depth, bool isTonePortamento) { prtmSSG_[ch] = depth; isTonePrtmSSG_[ch] = depth ? isTonePortamento : false; } void OPNAController::setVibratoEffectSSG(int ch, int period, int depth) { if (period && depth) vibItSSG_[ch] = std::make_unique(period, depth); else vibItSSG_[ch].reset(); } void OPNAController::setTremoloEffectSSG(int ch, int period, int depth) { if (period && depth) treItSSG_[ch] = std::make_unique(period, depth); else treItSSG_[ch].reset(); } void OPNAController::setVolumeSlideSSG(int ch, int depth, bool isUp) { volSldSSG_[ch] = depth * (isUp ? 1 : -1); } void OPNAController::setDetuneSSG(int ch, int pitch) { detuneSSG_[ch] = pitch; needToneSetSSG_[ch] = true; } void OPNAController::setNoteSlideSSG(int ch, int speed, int seminote) { if (seminote) { nsItSSG_[ch] = std::make_unique(speed, seminote); noteSldSSGSetFlag_ = true; } else nsItSSG_[ch].reset(); } void OPNAController::setTransposeEffectSSG(int ch, int seminote) { transposeSSG_[ch] += (seminote * 32); needToneSetSSG_[ch] = true; } void OPNAController::setToneNoiseMixSSG(int ch, int value) { toneNoiseMixSSG_[ch] = value; // Tone if ((tnSSG_[ch].isTone_ = (0x01 & value))) mixerSSG_ &= ~(1 << ch); else mixerSSG_ |= (1 << ch); // Noise if ((tnSSG_[ch].isNoise_ = (0x02 & value))) mixerSSG_ &= ~(1 << (ch + 3)); else mixerSSG_ |= (1 << (ch + 3)); opna_->setRegister(0x07, mixerSSG_); tnItSSG_[ch].reset(); } void OPNAController::setNoisePitchSSG(int ch, int pitch) { noisePitchSSG_ = pitch; tnSSG_[ch].noisePeriod_ = pitch; opna_->setRegister(0x06, static_cast(31 - pitch)); // Reverse order } void OPNAController::setHardEnvelopePeriod(int ch, bool high, int period) { bool sendable = isHardEnvSSG_[ch] && (CommandSequenceUnit::checkDataType(envSSG_[ch].data) == CommandSequenceUnit::RAW); if (high) { hardEnvPeriodHighSSG_ = period; if (sendable) { envSSG_[ch].data = (period << 8) | (envSSG_[ch].data & 0x00ff); envSSG_[ch].data |= ~(1 << 16); // raw data flag opna_->setRegister(0x0c, static_cast(period)); } } else { hardEnvPeriodLowSSG_ = period; if (sendable) { envSSG_[ch].data = (envSSG_[ch].data & 0xff00) | period; envSSG_[ch].data |= ~(1 << 16); // raw data flag opna_->setRegister(0x0b, static_cast(period)); } } } void OPNAController::setAutoEnvelopeSSG(int ch, int shift, int shape) { if (shape) { opna_->setRegister(0x0d, static_cast(shape)); switch (shape) { case 1: case 2: case 3: case 9: envSSG_[ch].type = 17; break; case 4: case 5: case 6: case 7: case 13: envSSG_[ch].type = 21; break; case 8: envSSG_[ch].type = 16; break; case 10: envSSG_[ch].type = 18; break; case 11: envSSG_[ch].type = 19; break; case 12: envSSG_[ch].type = 20; break; case 14: envSSG_[ch].type = 22; break; case 15: envSSG_[ch].type = 23; break; default: break; } opna_->setRegister(0x08 + static_cast(ch), 0x10); isHardEnvSSG_[ch] = true; if (shift == -8) { // Raw envSSG_[ch].data = (hardEnvPeriodHighSSG_ << 8) | hardEnvPeriodLowSSG_; opna_->setRegister(0x0c, static_cast(hardEnvPeriodHighSSG_)); opna_->setRegister(0x0b, static_cast(hardEnvPeriodLowSSG_)); } else { envSSG_[ch].data = CommandSequenceUnit::shift2data(shift); } } else { isHardEnvSSG_[ch] = false; envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; // Clear hard envelope in setRealVolumeSSG } needEnvSetSSG_[ch] = true; envItSSG_[ch].reset(); } /********** For state retrieve **********/ void OPNAController::haltSequencesSSG(int ch) { if (wfItSSG_[ch]) wfItSSG_[ch]->end(); if (treItSSG_[ch]) treItSSG_[ch]->end(); if (envItSSG_[ch]) envItSSG_[ch]->end(); if (tnItSSG_[ch]) tnItSSG_[ch]->end(); if (arpItSSG_[ch]) arpItSSG_[ch]->end(); if (ptItSSG_[ch]) ptItSSG_[ch]->end(); if (vibItSSG_[ch]) vibItSSG_[ch]->next(); if (nsItSSG_[ch]) nsItSSG_[ch]->end(); } /********** Mute **********/ void OPNAController::setMuteSSGState(int ch, bool isMute) { isMuteSSG_[ch] = isMute; if (isMute) { opna_->setRegister(0x08 + static_cast(ch), 0); isKeyOnSSG_[ch] = false; } } bool OPNAController::isMuteSSG(int ch) { return isMuteSSG_[ch]; } /********** Chip details **********/ bool OPNAController::isKeyOnSSG(int ch) const { return isKeyOnSSG_[ch]; } bool OPNAController::isTonePortamentoSSG(int ch) const { return isTonePrtmSSG_[ch]; } ToneDetail OPNAController::getSSGTone(int ch) const { return baseToneSSG_[ch].front(); } /***********************************/ void OPNAController::initSSG() { mixerSSG_ = 0xff; opna_->setRegister(0x07, mixerSSG_); // SSG mix noisePitchSSG_ = 0; hardEnvPeriodHighSSG_ = 0; hardEnvPeriodLowSSG_ = 0; for (int ch = 0; ch < 3; ++ch) { isKeyOnSSG_[ch] = false; refInstSSG_[ch].reset(); // Init envelope // Init echo buffer baseToneSSG_[ch] = std::deque(4); for (auto& td : baseToneSSG_[ch]) { td.octave = -1; } keyToneSSG_[ch].note = Note::C; // Dummy keyToneSSG_[ch].octave = -1; keyToneSSG_[ch].pitch = 0; // Dummy sumPitchSSG_[ch] = 0; tnSSG_[ch] = { false, false, CommandSequenceUnit::NODATA }; baseVolSSG_[ch] = 0xf; // Init volume tmpVolSSG_[ch] = -1; isHardEnvSSG_[ch] = false; isBuzzEffSSG_[ch] = false; // Init sequence hasPreSetTickEventSSG_[ch] = false; wfItSSG_[ch].reset(); wfSSG_[ch] = { SSGWaveFormType::UNSET, CommandSequenceUnit::NODATA }; envItSSG_[ch].reset(); envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; tnItSSG_[ch].reset(); arpItSSG_[ch].reset(); ptItSSG_[ch].reset(); needEnvSetSSG_[ch] = false; setHardEnvIfNecessary_[ch] = false; needMixSetSSG_[ch] = false; needToneSetSSG_[ch] = false; needSqMaskFreqSetSSG_[ch] = false; // Effect isArpEffSSG_[ch] = false; prtmSSG_[ch] = 0; isTonePrtmSSG_[ch] = false; vibItSSG_[ch].reset(); treItSSG_[ch].reset(); volSldSSG_[ch] = 0; sumVolSldSSG_[ch] = 0; detuneSSG_[ch] = 0; nsItSSG_[ch].reset(); sumNoteSldSSG_[ch] = 0; noteSldSSGSetFlag_ = false; transposeSSG_[ch] = 0; toneNoiseMixSSG_[ch] = 0; } } void OPNAController::setFrontSSGSequences(int ch) { if (isMuteSSG(ch)) return; setHardEnvIfNecessary_[ch] = false; if (wfItSSG_[ch]) writeWaveFormSSGToRegister(ch, wfItSSG_[ch]->front()); else writeSquareWaveForm(ch); if (treItSSG_[ch]) { treItSSG_[ch]->front(); needEnvSetSSG_[ch] = true; } if (volSldSSG_[ch]) { sumVolSldSSG_[ch] += volSldSSG_[ch]; needEnvSetSSG_[ch] = true; } if (volSldSSG_[ch]) { sumVolSldSSG_[ch] += volSldSSG_[ch]; needEnvSetSSG_[ch] = true; } if (envItSSG_[ch]) writeEnvelopeSSGToRegister(ch, envItSSG_[ch]->front()); else setRealVolumeSSG(ch); if (tnItSSG_[ch]) writeToneNoiseSSGToRegister(ch, tnItSSG_[ch]->front()); else if (needMixSetSSG_[ch]) writeToneNoiseSSGToRegisterNoReference(ch); if (arpItSSG_[ch]) checkRealToneSSGByArpeggio(ch, arpItSSG_[ch]->front()); checkPortamentoSSG(ch); if (ptItSSG_[ch]) checkRealToneSSGByPitch(ch, ptItSSG_[ch]->front()); if (vibItSSG_[ch]) { vibItSSG_[ch]->front(); needToneSetSSG_[ch] = true; } if (nsItSSG_[ch] && nsItSSG_[ch]->front() != -1) { sumNoteSldSSG_[ch] += nsItSSG_[ch]->getCommandType(); needToneSetSSG_[ch] = true; } writePitchSSG(ch); } void OPNAController::releaseStartSSGSequences(int ch) { if (isMuteSSG(ch)) return; setHardEnvIfNecessary_[ch] = false; if (wfItSSG_[ch]) writeWaveFormSSGToRegister(ch, wfItSSG_[ch]->next(true)); if (treItSSG_[ch]) { treItSSG_[ch]->next(true); needEnvSetSSG_[ch] = true; } if (volSldSSG_[ch]) { sumVolSldSSG_[ch] += volSldSSG_[ch]; needEnvSetSSG_[ch] = true; } if (envItSSG_[ch]) { int pos = envItSSG_[ch]->next(true); if (pos == -1) { opna_->setRegister(0x08 + static_cast(ch), 0); isHardEnvSSG_[ch] = false; } else writeEnvelopeSSGToRegister(ch, pos); } else { if (!hasPreSetTickEventSSG_[ch]) { opna_->setRegister(0x08 + static_cast(ch), 0); isHardEnvSSG_[ch] = false; } } if (tnItSSG_[ch]) writeToneNoiseSSGToRegister(ch, tnItSSG_[ch]->next(true)); else if (needMixSetSSG_[ch]) writeToneNoiseSSGToRegisterNoReference(ch); if (arpItSSG_[ch]) checkRealToneSSGByArpeggio(ch, arpItSSG_[ch]->next(true)); checkPortamentoSSG(ch); if (ptItSSG_[ch]) checkRealToneSSGByPitch(ch, ptItSSG_[ch]->next(true)); if (vibItSSG_[ch]) { vibItSSG_[ch]->next(true); needToneSetSSG_[ch] = true; } if (nsItSSG_[ch] && nsItSSG_[ch]->next(true) != -1) { sumNoteSldSSG_[ch] += nsItSSG_[ch]->getCommandType(); needToneSetSSG_[ch] = true; } if (needToneSetSSG_[ch] || (isHardEnvSSG_[ch] && needEnvSetSSG_[ch]) || needSqMaskFreqSetSSG_[ch]) writePitchSSG(ch); } void OPNAController::tickEventSSG(int ch) { if (hasPreSetTickEventSSG_[ch]) { hasPreSetTickEventSSG_[ch] = false; } else { if (isMuteSSG(ch)) return; setHardEnvIfNecessary_[ch] = false; if (wfItSSG_[ch]) writeWaveFormSSGToRegister(ch, wfItSSG_[ch]->next()); if (treItSSG_[ch]) { treItSSG_[ch]->next(); needEnvSetSSG_[ch] = true; } if (volSldSSG_[ch]) { sumVolSldSSG_[ch] += volSldSSG_[ch]; needEnvSetSSG_[ch] = true; } if (envItSSG_[ch]) { writeEnvelopeSSGToRegister(ch, envItSSG_[ch]->next()); } else if (needToneSetSSG_[ch] || needEnvSetSSG_[ch]) { setRealVolumeSSG(ch); } if (tnItSSG_[ch]) writeToneNoiseSSGToRegister(ch, tnItSSG_[ch]->next()); else if (needMixSetSSG_[ch]) writeToneNoiseSSGToRegisterNoReference(ch); if (arpItSSG_[ch]) checkRealToneSSGByArpeggio(ch, arpItSSG_[ch]->next()); checkPortamentoSSG(ch); if (ptItSSG_[ch]) checkRealToneSSGByPitch(ch, ptItSSG_[ch]->next()); if (vibItSSG_[ch]) { vibItSSG_[ch]->next(); needToneSetSSG_[ch] = true; } if (nsItSSG_[ch] && nsItSSG_[ch]->next() != -1) { sumNoteSldSSG_[ch] += nsItSSG_[ch]->getCommandType(); needToneSetSSG_[ch] = true; } if (needToneSetSSG_[ch] || (isHardEnvSSG_[ch] && needEnvSetSSG_[ch]) || needSqMaskFreqSetSSG_[ch]) writePitchSSG(ch); } } void OPNAController::writeWaveFormSSGToRegister(int ch, int seqPos) { if (seqPos == -1) return; switch (static_cast(wfItSSG_[ch]->getCommandType())) { case SSGWaveFormType::SQUARE: { writeSquareWaveForm(ch); return; } case SSGWaveFormType::TRIANGLE: { if (wfSSG_[ch].type == SSGWaveFormType::TRIANGLE && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: needMixSetSSG_[ch] = true; break; default: break; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: opna_->setRegister(0x0d, 0x0e); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x0e); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; needSqMaskFreqSetSSG_[ch] = false; wfSSG_[ch] = { SSGWaveFormType::TRIANGLE, CommandSequenceUnit::NODATA }; return; } case SSGWaveFormType::SAW: { if (wfSSG_[ch].type == SSGWaveFormType::SAW && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: needMixSetSSG_[ch] = true; break; default: break; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::INVSAW: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_INVSAW: opna_->setRegister(0x0d, 0x0c); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x0c); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; needSqMaskFreqSetSSG_[ch] = false; wfSSG_[ch] = { SSGWaveFormType::SAW, CommandSequenceUnit::NODATA }; return; } case SSGWaveFormType::INVSAW: { if (wfSSG_[ch].type == SSGWaveFormType::INVSAW && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: needMixSetSSG_[ch] = true; break; default: break; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: opna_->setRegister(0x0d, 0x08); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x08); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; needSqMaskFreqSetSSG_[ch] = false; wfSSG_[ch] = { SSGWaveFormType::INVSAW, CommandSequenceUnit::NODATA }; return; } case SSGWaveFormType::SQM_TRIANGLE: { int data = wfItSSG_[ch]->getCommandData(); if (wfSSG_[ch].type == SSGWaveFormType::SQM_TRIANGLE && wfSSG_[ch].data == data && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: needMixSetSSG_[ch] = true; break; default: break; } if (wfSSG_[ch].data != data) { if (CommandSequenceUnit::checkDataType(data) == CommandSequenceUnit::RATIO) { needSqMaskFreqSetSSG_[ch] = true; } else { uint16_t pitch = static_cast(data); uint8_t offset = static_cast(ch << 1); opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); needSqMaskFreqSetSSG_[ch] = false; } } else { needSqMaskFreqSetSSG_[ch] = false; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: opna_->setRegister(0x0d, 0x0e); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x0e); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; wfSSG_[ch] = { SSGWaveFormType::SQM_TRIANGLE, data }; return; } case SSGWaveFormType::SQM_SAW: { int data = wfItSSG_[ch]->getCommandData(); if (wfSSG_[ch].type == SSGWaveFormType::SQM_SAW && wfSSG_[ch].data == data && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: needMixSetSSG_[ch] = true; break; default: break; } if (wfSSG_[ch].data != data) { if (CommandSequenceUnit::checkDataType(data) == CommandSequenceUnit::RATIO) { needSqMaskFreqSetSSG_[ch] = true; } else { uint16_t pitch = static_cast(data); uint8_t offset = static_cast(ch << 1); opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); needSqMaskFreqSetSSG_[ch] = false; } } else { needSqMaskFreqSetSSG_[ch] = false; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::INVSAW: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_INVSAW: opna_->setRegister(0x0d, 0x0c); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x0c); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; wfSSG_[ch] = { SSGWaveFormType::SQM_SAW, data }; return; } case SSGWaveFormType::SQM_INVSAW: { int data = wfItSSG_[ch]->getCommandData(); if (wfSSG_[ch].type == SSGWaveFormType::SQM_INVSAW && wfSSG_[ch].data == data && isKeyOnSSG_[ch]) return; switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: needMixSetSSG_[ch] = true; break; default: break; } if (wfSSG_[ch].data != data) { if (CommandSequenceUnit::checkDataType(data) == CommandSequenceUnit::RATIO) { needSqMaskFreqSetSSG_[ch] = true; } else { uint16_t pitch = static_cast(data); uint8_t offset = static_cast(ch << 1); opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); needSqMaskFreqSetSSG_[ch] = false; } } else { needSqMaskFreqSetSSG_[ch] = false; } switch (wfSSG_[ch].type) { case SSGWaveFormType::UNSET: case SSGWaveFormType::SQUARE: case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: opna_->setRegister(0x0d, 0x08); break; default: if (!isKeyOnSSG_[ch]) opna_->setRegister(0x0d, 0x08); // First key on break; } if (isHardEnvSSG_[ch]) { isBuzzEffSSG_[ch] = true; isHardEnvSSG_[ch] = false; } else if (!isBuzzEffSSG_[ch] || !isKeyOnSSG_[ch]) { isBuzzEffSSG_[ch] = true; opna_->setRegister(0x08 + static_cast(ch), 0x10); } if (envSSG_[ch].type == 0) envSSG_[ch] = { -1, CommandSequenceUnit::NODATA }; needEnvSetSSG_[ch] = false; needToneSetSSG_[ch] = true; wfSSG_[ch] = { SSGWaveFormType::SQM_INVSAW, data }; return; } default: break; } } void OPNAController::writeSquareWaveForm(int ch) { if (wfSSG_[ch].type == SSGWaveFormType::SQUARE) { if (!isKeyOnSSG_[ch]) { needEnvSetSSG_[ch] = true; needToneSetSSG_[ch] = true; } return; } switch (wfSSG_[ch].type) { case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: break; default: { needMixSetSSG_[ch] = true; break; } } if (isBuzzEffSSG_[ch]) { isBuzzEffSSG_[ch] = false; setHardEnvIfNecessary_[ch] = true; } needEnvSetSSG_[ch] = true; needToneSetSSG_[ch] = true; needSqMaskFreqSetSSG_[ch] = false; wfSSG_[ch] = { SSGWaveFormType::SQUARE, CommandSequenceUnit::NODATA }; } void OPNAController::writeToneNoiseSSGToRegister(int ch, int seqPos) { if (seqPos == -1) { if (needMixSetSSG_[ch]) writeToneNoiseSSGToRegisterNoReference(ch); return; } int type = tnItSSG_[ch]->getCommandType(); if (type == -1) return; else if (!type) { // tone if (tnSSG_[ch].isTone_) { if (tnSSG_[ch].isNoise_) { mixerSSG_ |= (1 << (ch + 3)); switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: mixerSSG_ |= (1 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { false, false, CommandSequenceUnit::NODATA }; break; default: opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { true, false, CommandSequenceUnit::NODATA }; break; } } else { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: mixerSSG_ |= (1 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { false, false, CommandSequenceUnit::NODATA }; break; default: break; } } } else { if (tnSSG_[ch].isNoise_) { mixerSSG_ |= (1 << (ch + 3)); switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { false, false, CommandSequenceUnit::NODATA }; break; default: mixerSSG_ &= ~(1 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { true, false, CommandSequenceUnit::NODATA }; break; } } else { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: break; default: mixerSSG_ &= ~(1 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch] = { true, false, CommandSequenceUnit::NODATA }; break; } } } } else { if (type > 32) { // Tone&Noise if (tnSSG_[ch].isNoise_) { if (tnSSG_[ch].isTone_) { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: mixerSSG_ |= (1 << ch); tnSSG_[ch].isTone_ = false; opna_->setRegister(0x07, mixerSSG_); break; default: break; } } else { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: break; default: mixerSSG_ &= ~(1 << ch); tnSSG_[ch].isTone_ = true; opna_->setRegister(0x07, mixerSSG_); break; } } } else { if (tnSSG_[ch].isTone_) { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: mixerSSG_ |= (1 << ch); tnSSG_[ch].isTone_ = false; break; default: break; } } else { switch (wfSSG_[ch].type) { case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: break; default: mixerSSG_ &= ~(1 << ch); tnSSG_[ch].isTone_ = true; break; } } mixerSSG_ &= ~(1 << (ch + 3)); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch].isNoise_ = true; } if (!tnSSG_[ch].isTone_ || !tnSSG_[ch].isNoise_) { mixerSSG_ &= ~(0x1001 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch].isTone_ = true; tnSSG_[ch].isNoise_ = true; } int p = type - 33; if (tnSSG_[ch].noisePeriod_ != p) { opna_->setRegister(0x06, static_cast(31 - p)); // Reverse order tnSSG_->noisePeriod_ = p; } } else { // Noise if (tnSSG_[ch].isNoise_) { if (tnSSG_[ch].isTone_) { mixerSSG_ |= (1 << ch); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch].isTone_ = false; } } else { if (tnSSG_[ch].isTone_) { mixerSSG_ |= (1 << ch); tnSSG_[ch].isTone_ = false; } mixerSSG_ &= ~(1 << (ch + 3)); opna_->setRegister(0x07, mixerSSG_); tnSSG_[ch].isNoise_ = true; } int p = type - 1; if (tnSSG_[ch].noisePeriod_ != p) { opna_->setRegister(0x06, static_cast(31- p)); // Reverse order tnSSG_->noisePeriod_ = p; } } } needMixSetSSG_[ch] = false; } void OPNAController::writeToneNoiseSSGToRegisterNoReference(int ch) { switch (wfSSG_[ch].type) { case SSGWaveFormType::SQUARE: case SSGWaveFormType::SQM_TRIANGLE: case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: mixerSSG_ &= ~(1 << ch); tnSSG_[ch].isTone_ = true; break; case SSGWaveFormType::TRIANGLE: case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: mixerSSG_ |= (1 << ch); tnSSG_[ch].isNoise_ = false; break; default: break; } opna_->setRegister(0x07, mixerSSG_); needMixSetSSG_[ch] = false; } void OPNAController::writeEnvelopeSSGToRegister(int ch, int seqPos) { if (isBuzzEffSSG_[ch]) return; if (seqPos == -1) { if (needEnvSetSSG_[ch]) { setRealVolumeSSG(ch); needEnvSetSSG_[ch] = false; } return; } int type = envItSSG_[ch]->getCommandType(); if (type == -1) return; else if (type < 16) { // Software envelope isHardEnvSSG_[ch] = false; envSSG_[ch] = { type, CommandSequenceUnit::NODATA }; setRealVolumeSSG(ch); needEnvSetSSG_[ch] = false; } else { // Hardware envelope int data = envItSSG_[ch]->getCommandData(); if (envSSG_[ch].data != data || setHardEnvIfNecessary_[ch]) { envSSG_[ch].data = data; if (CommandSequenceUnit::checkDataType(data) == CommandSequenceUnit::RATIO) { /* Envelope period is set in writePitchSSG */ needEnvSetSSG_[ch] = true; } else { opna_->setRegister(0x0b, 0x00ff & envSSG_[ch].data); opna_->setRegister(0x0c, static_cast(envSSG_[ch].data >> 8)); needEnvSetSSG_[ch] = false; } } else { needEnvSetSSG_[ch] = false; } if (envSSG_[ch].type != type || !isKeyOnSSG_[ch] || setHardEnvIfNecessary_[ch]) { opna_->setRegister(0x0d, static_cast(type - 16 + 8)); envSSG_[ch].type = type; if (CommandSequenceUnit::checkDataType(data) == CommandSequenceUnit::RATIO) needEnvSetSSG_[ch] = true; } if (!isHardEnvSSG_[ch]) { opna_->setRegister(static_cast(0x08 + ch), 0x10); isHardEnvSSG_[ch] = true; } // setHardEnvIfNecessary_[ch] = false; } } void OPNAController::checkRealToneSSGByArpeggio(int ch, int seqPos) { if (seqPos == -1) return; int type = arpItSSG_[ch]->getCommandType(); if (type == -1) return; switch (arpItSSG_[ch]->getSequenceType()) { case SequenceType::ABSOLUTE_SEQUENCE: { std::pair pair = noteNumberToOctaveAndNote( octaveAndNoteToNoteNumber(baseToneSSG_[ch].front().octave, baseToneSSG_[ch].front().note) + type - 48); keyToneSSG_[ch].octave = pair.first; keyToneSSG_[ch].note = pair.second; break; } case SequenceType::FIXED_SEQUENCE: { std::pair pair = noteNumberToOctaveAndNote(type); keyToneSSG_[ch].octave = pair.first; keyToneSSG_[ch].note = pair.second; break; } case SequenceType::RELATIVE_SEQUENCE: { std::pair pair = noteNumberToOctaveAndNote( octaveAndNoteToNoteNumber(keyToneSSG_[ch].octave, keyToneSSG_[ch].note) + type - 48); keyToneSSG_[ch].octave = pair.first; keyToneSSG_[ch].note = pair.second; break; } default: break; } needToneSetSSG_[ch] = true; } void OPNAController::checkPortamentoSSG(int ch) { if ((!arpItSSG_[ch] || arpItSSG_[ch]->getPosition() == -1) && prtmSSG_[ch]) { if (isTonePrtmSSG_[ch]) { int dif = ( octaveAndNoteToNoteNumber(baseToneSSG_[ch].front().octave, baseToneSSG_[ch].front().note) * 32 + baseToneSSG_[ch].front().pitch ) - ( octaveAndNoteToNoteNumber(keyToneSSG_[ch].octave, keyToneSSG_[ch].note) * 32 + keyToneSSG_[ch].pitch ); if (dif > 0) { if (dif - prtmSSG_[ch] < 0) { keyToneSSG_[ch] = baseToneSSG_[ch].front(); } else { keyToneSSG_[ch].pitch += prtmSSG_[ch]; } needToneSetSSG_[ch] = true; } else if (dif < 0) { if (dif + prtmSSG_[ch] > 0) { keyToneSSG_[ch] = baseToneSSG_[ch].front(); } else { keyToneSSG_[ch].pitch -= prtmSSG_[ch]; } needToneSetSSG_[ch] = true; } } else { keyToneSSG_[ch].pitch += prtmSSG_[ch]; needToneSetSSG_[ch] = true; } } } void OPNAController::checkRealToneSSGByPitch(int ch, int seqPos) { if (seqPos == -1) return; int diff = ptItSSG_[ch]->getCommandType() - 127; if (diff < -127) return; switch (ptItSSG_[ch]->getSequenceType()) { case SequenceType::ABSOLUTE_SEQUENCE: sumPitchSSG_[ch] = diff; break; case SequenceType::RELATIVE_SEQUENCE: sumPitchSSG_[ch] += diff; break; default: break; } needToneSetSSG_[ch] = true; } void OPNAController::writePitchSSG(int ch) { if (keyToneSSG_[ch].octave == -1) return; // Not set note yet int p = keyToneSSG_[ch].pitch + sumPitchSSG_[ch] + (vibItSSG_[ch] ? vibItSSG_[ch]->getCommandType() : 0) + detuneSSG_[ch] + sumNoteSldSSG_[ch] + transposeSSG_[ch]; switch (wfSSG_[ch].type) { case SSGWaveFormType::SQUARE: { uint16_t pitch = PitchConverter::getPitchSSGSquare( keyToneSSG_[ch].note, keyToneSSG_[ch].octave, p); if (needToneSetSSG_[ch]) { uint8_t offset = static_cast(ch << 1); opna_->setRegister(0x00 + offset, pitch & 0xff); opna_->setRegister(0x01 + offset, pitch >> 8); writeAutoEnvelopePitchSSG(ch, pitch); } else if (isHardEnvSSG_[ch] && needEnvSetSSG_[ch]) { writeAutoEnvelopePitchSSG(ch, pitch); } break; } case SSGWaveFormType::TRIANGLE: if (needToneSetSSG_[ch]) { uint16_t pitch = PitchConverter::getPitchSSGTriangle( keyToneSSG_[ch].note, keyToneSSG_[ch].octave, p); opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); } break; case SSGWaveFormType::SAW: case SSGWaveFormType::INVSAW: if (needToneSetSSG_[ch]){ uint16_t pitch = PitchConverter::getPitchSSGSaw( keyToneSSG_[ch].note, keyToneSSG_[ch].octave, p); opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); } break; case SSGWaveFormType::SQM_TRIANGLE: { uint16_t pitch = PitchConverter::getPitchSSGTriangle( keyToneSSG_[ch].note, keyToneSSG_[ch].octave, p); if (needToneSetSSG_[ch]) { opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); if (CommandSequenceUnit::checkDataType(wfSSG_[ch].data) == CommandSequenceUnit::RATIO) { writeSquareMaskPitchSSG(ch, pitch, true); } } else if (needSqMaskFreqSetSSG_[ch]) { if (CommandSequenceUnit::checkDataType(wfSSG_[ch].data) == CommandSequenceUnit::RATIO) { writeSquareMaskPitchSSG(ch, pitch, true); } } break; } case SSGWaveFormType::SQM_SAW: case SSGWaveFormType::SQM_INVSAW: { uint16_t pitch = PitchConverter::getPitchSSGSaw( keyToneSSG_[ch].note, keyToneSSG_[ch].octave, p); if (needToneSetSSG_[ch]) { opna_->setRegister(0x0b, pitch & 0x00ff); opna_->setRegister(0x0c, pitch >> 8); if (CommandSequenceUnit::checkDataType(wfSSG_[ch].data) == CommandSequenceUnit::RATIO) { writeSquareMaskPitchSSG(ch, pitch, false); } } else if (needSqMaskFreqSetSSG_[ch]) { if (CommandSequenceUnit::checkDataType(wfSSG_[ch].data) == CommandSequenceUnit::RATIO) { writeSquareMaskPitchSSG(ch, pitch, false); } } break; } default: break; } needToneSetSSG_[ch] = false; needEnvSetSSG_[ch] = false; needSqMaskFreqSetSSG_[ch] = false; } void OPNAController::writeAutoEnvelopePitchSSG(int ch, double tonePitch) { // Multiple frequency if triangle int div = (envSSG_[ch].type == 18 || envSSG_[ch].type == 22) ? 32 : 16; CommandSequenceUnit::DataType type = CommandSequenceUnit::checkDataType(envSSG_[ch].data); switch (type) { case CommandSequenceUnit::RATIO: { auto ratio = CommandSequenceUnit::data2ratio(envSSG_[ch].data); uint16_t period = static_cast(std::round(tonePitch * ratio.first / (ratio.second * div))); opna_->setRegister(0x0b, 0x00ff & period); opna_->setRegister(0x0c, static_cast(period >> 8)); break; } case CommandSequenceUnit::LSHIFT: case CommandSequenceUnit::RSHIFT: { uint16_t period = static_cast(std::round(tonePitch / div)); int shift = CommandSequenceUnit::data2shift(envSSG_[ch].data); shift = (type == CommandSequenceUnit::LSHIFT) ? -shift : shift; shift -= 4; // Adjust rate to that of 0CC-FamiTracker if (shift < 0) period <<= -shift; else period >>= shift; opna_->setRegister(0x0b, 0x00ff & period); opna_->setRegister(0x0c, static_cast(period >> 8)); break; } default: break; } } void OPNAController::writeSquareMaskPitchSSG(int ch, double tonePitch, bool isTriangle) { int mul = isTriangle ? 32 : 16; // Multiple frequency if triangle auto ratio = CommandSequenceUnit::data2ratio(wfSSG_[ch].data); // Calculate mask period uint16_t period = static_cast(std::round(ratio.first * mul * tonePitch / ratio.second)); uint8_t offset = static_cast(ch << 1); opna_->setRegister(0x00 + offset, period & 0x00ff); opna_->setRegister(0x01 + offset, period >> 8); } //---------- Drum ----------// /********** Key on-off **********/ void OPNAController::setKeyOnFlagDrum(int ch) { if (isMuteDrum(ch)) return; if (tmpVolDrum_[ch] != -1) setVolumeDrum(ch, volDrum_[ch]); keyOnFlagDrum_ |= static_cast(1 << ch); } void OPNAController::setKeyOffFlagDrum(int ch) { keyOffFlagDrum_ |= static_cast(1 << ch); } /********** Set volume **********/ void OPNAController::setVolumeDrum(int ch, int volume) { if (volume > 0x1f) return; // Out of range volDrum_[ch] = volume; tmpVolDrum_[ch] = -1; opna_->setRegister(0x18 + static_cast(ch), static_cast((panDrum_[ch] << 6) | volume)); } void OPNAController::setMasterVolumeDrum(int volume) { mVolDrum_ = volume; opna_->setRegister(0x11, static_cast(volume)); } void OPNAController::setTemporaryVolumeDrum(int ch, int volume) { if (volume > 0x1f) return; // Out of range tmpVolDrum_[ch] = volume; opna_->setRegister(0x18 + static_cast(ch), static_cast((panDrum_[ch] << 6) | volume)); } /********** Set pan **********/ void OPNAController::setPanDrum(int ch, int value) { panDrum_[ch] = static_cast(value); opna_->setRegister(0x18 + static_cast(ch), static_cast((value << 6) | volDrum_[ch])); } /********** Mute **********/ void OPNAController::setMuteDrumState(int ch, bool isMute) { isMuteDrum_[ch] = isMute; if (isMute) { setKeyOffFlagDrum(ch); updateKeyOnOffStatusDrum(); } } bool OPNAController::isMuteDrum(int ch) { return isMuteDrum_[ch]; } /***********************************/ void OPNAController::initDrum() { keyOnFlagDrum_ = 0; keyOffFlagDrum_ = 0; mVolDrum_ = 0x3f; opna_->setRegister(0x11, 0x3f); // Drum total volume for (int ch = 0; ch < 6; ++ch) { volDrum_[ch] = 0x1f; // Init volume tmpVolDrum_[ch] = -1; // Init pan panDrum_[ch] = 3; opna_->setRegister(0x18 + static_cast(ch), 0xdf); } } void OPNAController::updateKeyOnOffStatusDrum() { if (keyOnFlagDrum_) { opna_->setRegister(0x10, keyOnFlagDrum_); keyOnFlagDrum_ = 0; } if (keyOffFlagDrum_) { opna_->setRegister(0x10, 0x80 | keyOffFlagDrum_); keyOffFlagDrum_ = 0; } } BambooTracker-0.3.5/BambooTracker/opna_controller.hpp000066400000000000000000000302051362177441300226660ustar00rootroot00000000000000#pragma once #include #include #include #include #include "opna.hpp" #include "instrument.hpp" #include "effect_iterator.hpp" #include "chips/chip_misc.h" #include "chips/scci/scci.h" #include "chips/c86ctl/c86ctl_wrapper.hpp" #include "enum_hash.hpp" #include "misc.hpp" struct RegisterUnit { int address, value; bool hasCompleted; }; struct ToneDetail { int octave; Note note; int pitch; }; struct ToneNoise { bool isTone_, isNoise_; int noisePeriod_; }; struct WaveForm { SSGWaveFormType type; int data; // Same format with CommandSequenceUnit::data }; class OPNAController { public: OPNAController(chip::Emu emu, int clock, int rate, int duration); // Reset and initialize void reset(); // Forward instrument sequence void tickEvent(SoundSource src, int ch); // Direct register set void sendRegisterAddress(int bank, int address); void sendRegisterValue(int value); // Update register states after tick process void updateRegisterStates(); // Real chip interface void useSCCI(scci::SoundInterfaceManager* manager); bool isUsedSCCI() const; void useC86CTL(C86ctlBase* base); bool isUsedC86CTL() const; // Stream samples void getStreamSamples(int16_t* container, size_t nSamples); void getOutputHistory(int16_t* history); static constexpr int OUTPUT_HISTORY_SIZE = 1024; // Chip mode void setMode(SongType mode); SongType getMode() const; // Stream details int getRate() const; void setRate(int rate); int getDuration() const; void setDuration(int duration); void setMasterVolume(int percentage); // Export void setExportContainer(std::shared_ptr cntr = nullptr); private: std::unique_ptr opna_; SongType mode_; std::vector registerSetBuf_; void initChip(); void fillOutputHistory(const int16_t* outputs, size_t nSamples); void transferReadyHistory(); /*----- FM -----*/ public: // Key on-off void keyOnFM(int ch, Note note, int octave, int pitch, bool isJam = false); void keyOnFM(int ch, int echoBuf); void keyOffFM(int ch, bool isJam = false); void updateEchoBufferFM(int ch, int octave, Note note, int pitch); // Set Instrument void setInstrumentFM(int ch, std::shared_ptr inst); void updateInstrumentFM(int instNum); void updateInstrumentFMEnvelopeParameter(int envNum, FMEnvelopeParameter param); void setInstrumentFMOperatorEnabled(int envNum, int opNum); void updateInstrumentFMLFOParameter(int lfoNum, FMLFOParameter param); void resetFMChannelEnvelope(int ch); void restoreFMEnvelopeFromReset(int ch); // Set volume void setVolumeFM(int ch, int volume); void setTemporaryVolumeFM(int ch, int volume); void setMasterVolumeFM(double dB); // Set pan void setPanFM(int ch, int value); // Set effect void setArpeggioEffectFM(int ch, int second, int third); void setPortamentoEffectFM(int ch, int depth, bool isTonePortamento = false); void setVibratoEffectFM(int ch, int period, int depth); void setTremoloEffectFM(int ch, int period, int depth); void setVolumeSlideFM(int ch, int depth, bool isUp); void setDetuneFM(int ch, int pitch); void setNoteSlideFM(int ch, int speed, int seminote); void setTransposeEffectFM(int ch, int seminote); void setFBControlFM(int ch, int value); void setTLControlFM(int ch, int op, int value); void setMLControlFM(int ch, int op, int value); void setARControlFM(int ch, int op, int value); void setDRControlFM(int ch, int op, int value); void setRRControlFM(int ch, int op, int value); void setBrightnessFM(int ch, int value); // For state retrieve void haltSequencesFM(int ch); // Mute void setMuteFMState(int ch, bool isMuteFM); bool isMuteFM(int ch); // Chip details bool isKeyOnFM(int ch) const; bool isTonePortamentoFM(int ch) const; bool enableFMEnvelopeReset(int ch) const; ToneDetail getFMTone(int ch) const; private: std::shared_ptr refInstFM_[6]; std::unique_ptr envFM_[6]; bool isKeyOnFM_[9]; uint8_t fmOpEnables_[6]; std::deque baseToneFM_[9]; ToneDetail keyToneFM_[9]; int sumPitchFM_[9]; int baseVolFM_[9], tmpVolFM_[9]; /// bit0: right on/off /// bit1: left on/off uint8_t panFM_[6]; bool isMuteFM_[9]; bool enableEnvResetFM_[9], hasResetEnvFM_[9]; int lfoFreq_; int lfoStartCntFM_[6]; bool hasPreSetTickEventFM_[9]; bool needToneSetFM_[9]; std::unordered_map> opSeqItFM_[6]; std::unique_ptr arpItFM_[9]; std::unique_ptr ptItFM_[9]; bool isArpEffFM_[9]; int prtmFM_[9]; bool isTonePrtmFM_[9]; std::unique_ptr vibItFM_[9]; std::unique_ptr treItFM_[9]; int volSldFM_[9], sumVolSldFM_[9]; int detuneFM_[9]; std::unique_ptr nsItFM_[9]; int sumNoteSldFM_[9]; bool noteSldFMSetFlag_[9]; int transposeFM_[9]; bool isFBCtrlFM_[6], isTLCtrlFM_[6][4], isMLCtrlFM_[6][4], isARCtrlFM_[6][4]; bool isDRCtrlFM_[6][4], isRRCtrlFM_[6][4]; bool isBrightFM_[6][4]; void initFM(); int toInternalFMChannel(int ch) const; uint8_t getFMKeyOnOffChannelMask(int ch) const; uint32_t getFMChannelOffset(int ch, bool forPitch = false) const; FMOperatorType toChannelOperatorType(int ch) const; std::vector getFMEnvelopeParametersForOperator(FMOperatorType op) const; void updateFMVolume(int ch); void writeFMEnvelopeToRegistersFromInstrument(int inch); void writeFMEnveropeParameterToRegister(int inch, FMEnvelopeParameter param, int value); void writeFMLFOAllRegisters(int inch); void writeFMLFORegister(int inch, FMLFOParameter param); void checkLFOUsed(); void setFrontFMSequences(int ch); void releaseStartFMSequences(int ch); void tickEventFM(int ch); void checkOperatorSequenceFM(int ch, int type); void checkVolumeEffectFM(int ch); void checkRealToneFMByArpeggio(int ch, int seqPos); void checkPortamentoFM(int ch); void checkRealToneFMByPitch(int ch, int seqPos); void writePitchFM(int ch); void setInstrumentFMProperties(int ch); static bool isCarrier(int op, int al); static std::vector getOperatorsInLevel(int level, int al); inline FMEnvelopeParameter getParameterTL(int op) const { switch (op) { case 0: return FMEnvelopeParameter::TL1; case 1: return FMEnvelopeParameter::TL2; case 2: return FMEnvelopeParameter::TL3; case 3: return FMEnvelopeParameter::TL4; default: throw std::invalid_argument("Invalid operator value."); } } inline FMEnvelopeParameter getParameterML(int op) const { switch (op) { case 0: return FMEnvelopeParameter::ML1; case 1: return FMEnvelopeParameter::ML2; case 2: return FMEnvelopeParameter::ML3; case 3: return FMEnvelopeParameter::ML4; default: throw std::invalid_argument("Invalid operator value."); } } inline FMEnvelopeParameter getParameterAR(int op) const { switch (op) { case 0: return FMEnvelopeParameter::AR1; case 1: return FMEnvelopeParameter::AR2; case 2: return FMEnvelopeParameter::AR3; case 3: return FMEnvelopeParameter::AR4; default: throw std::invalid_argument("Invalid operator value."); } } inline FMEnvelopeParameter getParameterDR(int op) const { switch (op) { case 0: return FMEnvelopeParameter::DR1; case 1: return FMEnvelopeParameter::DR2; case 2: return FMEnvelopeParameter::DR3; case 3: return FMEnvelopeParameter::DR4; default: throw std::invalid_argument("Invalid operator value."); } } inline FMEnvelopeParameter getParameterRR(int op) const { switch (op) { case 0: return FMEnvelopeParameter::RR1; case 1: return FMEnvelopeParameter::RR2; case 2: return FMEnvelopeParameter::RR3; case 3: return FMEnvelopeParameter::RR4; default: throw std::invalid_argument("Invalid operator value."); } } inline uint8_t calculateTL(int ch, uint8_t data) const { int v = (tmpVolFM_[ch] == -1) ? baseVolFM_[ch] : tmpVolFM_[ch]; return (data > 127 - v) ? 127 : static_cast(data + v); } /*----- SSG -----*/ public: // Key on-off void keyOnSSG(int ch, Note note, int octave, int pitch, bool isJam = false); void keyOnSSG(int ch, int echoBuf); void keyOffSSG(int ch, bool isJam = false); void updateEchoBufferSSG(int ch, int octave, Note note, int pitch); // Set Instrument void setInstrumentSSG(int ch, std::shared_ptr inst); void updateInstrumentSSG(int instNum); // Set volume void setVolumeSSG(int ch, int volume); void setTemporaryVolumeSSG(int ch, int volume); void setMasterVolumeSSG(double dB); // Set effect void setArpeggioEffectSSG(int ch, int second, int third); void setPortamentoEffectSSG(int ch, int depth, bool isTonePortamento = false); void setVibratoEffectSSG(int ch, int period, int depth); void setTremoloEffectSSG(int ch, int period, int depth); void setVolumeSlideSSG(int ch, int depth, bool isUp); void setDetuneSSG(int ch, int pitch); void setNoteSlideSSG(int ch, int speed, int seminote); void setTransposeEffectSSG(int ch, int seminote); void setToneNoiseMixSSG(int ch, int value); void setNoisePitchSSG(int ch, int pitch); void setHardEnvelopePeriod(int ch, bool high, int period); void setAutoEnvelopeSSG(int ch, int shift, int shape); // For state retrieve void haltSequencesSSG(int ch); // Mute void setMuteSSGState(int ch, bool isMuteFM); bool isMuteSSG(int ch); // Chip details bool isKeyOnSSG(int ch) const; bool isTonePortamentoSSG(int ch) const; ToneDetail getSSGTone(int ch) const; private: std::shared_ptr refInstSSG_[3]; bool isKeyOnSSG_[3]; uint8_t mixerSSG_; std::deque baseToneSSG_[3]; ToneDetail keyToneSSG_[3]; int sumPitchSSG_[3]; ToneNoise tnSSG_[3]; int baseVolSSG_[3], tmpVolSSG_[3]; bool isBuzzEffSSG_[3]; bool isHardEnvSSG_[3]; bool isMuteSSG_[3]; bool hasPreSetTickEventSSG_[3]; bool needEnvSetSSG_[3]; bool setHardEnvIfNecessary_[3]; bool needMixSetSSG_[3]; bool needToneSetSSG_[3]; bool needSqMaskFreqSetSSG_[3]; std::unique_ptr wfItSSG_[3]; WaveForm wfSSG_[3]; std::unique_ptr envItSSG_[3]; CommandSequenceUnit envSSG_[3]; std::unique_ptr tnItSSG_[3]; std::unique_ptr arpItSSG_[3]; std::unique_ptr ptItSSG_[3]; bool isArpEffSSG_[3]; int prtmSSG_[3]; bool isTonePrtmSSG_[3]; std::unique_ptr vibItSSG_[3]; std::unique_ptr treItSSG_[3]; int volSldSSG_[3], sumVolSldSSG_[3]; int detuneSSG_[3]; std::unique_ptr nsItSSG_[3]; int sumNoteSldSSG_[3]; bool noteSldSSGSetFlag_; int transposeSSG_[3]; int toneNoiseMixSSG_[3]; int noisePitchSSG_; int hardEnvPeriodHighSSG_, hardEnvPeriodLowSSG_; std::unique_ptr outputHistory_; size_t outputHistoryIndex_; std::unique_ptr outputHistoryReady_; std::mutex outputHistoryReadyMutex_; void initSSG(); void setFrontSSGSequences(int ch); void releaseStartSSGSequences(int ch); void tickEventSSG(int ch); void writeWaveFormSSGToRegister(int ch, int seqPos); void writeSquareWaveForm(int ch); void writeToneNoiseSSGToRegister(int ch, int seqPos); void writeToneNoiseSSGToRegisterNoReference(int ch); void writeEnvelopeSSGToRegister(int ch, int seqPos); void checkRealToneSSGByArpeggio(int ch, int seqPos); void checkPortamentoSSG(int ch); void checkRealToneSSGByPitch(int ch, int seqPos); void writePitchSSG(int ch); void writeAutoEnvelopePitchSSG(int ch, double tonePitch); void writeSquareMaskPitchSSG(int ch, double tonePitch, bool isTriangle); void setRealVolumeSSG(int ch); inline uint8_t judgeSSEGRegisterValue(int v) { return (v == -1) ? 0 : (0x08 + static_cast(v)); } /*----- Drum -----*/ public: // Key on-off void setKeyOnFlagDrum(int ch); void setKeyOffFlagDrum(int ch); // Set volume void setVolumeDrum(int ch, int volume); void setMasterVolumeDrum(int volume); void setTemporaryVolumeDrum(int ch, int volume); // Set pan void setPanDrum(int ch, int value); // Mute void setMuteDrumState(int ch, bool isMute); bool isMuteDrum(int ch); private: uint8_t keyOnFlagDrum_, keyOffFlagDrum_; int volDrum_[6], mVolDrum_, tmpVolDrum_[6]; /// bit0: right on/off /// bit1: left on/off uint8_t panDrum_[6]; bool isMuteDrum_[6]; void initDrum(); void updateKeyOnOffStatusDrum(); }; BambooTracker-0.3.5/BambooTracker/pitch_converter.cpp000066400000000000000000002617241362177441300226730ustar00rootroot00000000000000#include "pitch_converter.hpp" uint16_t PitchConverter::getPitchFM(Note note, int octave, int pitch) { return centTableFM_[calculateIndex(octave, note, pitch)]; } uint16_t PitchConverter::getPitchSSGSquare(Note note, int octave, int pitch) { return centTableSSGSquare_[calculateIndex(octave, note, pitch)]; } uint16_t PitchConverter::getPitchSSGSquare(int n) { return centTableSSGSquare_[n]; } uint16_t PitchConverter::getPitchSSGTriangle(Note note, int octave, int pitch) { return centTableSSGTriangle_[calculateIndex(octave, note, pitch)]; } uint16_t PitchConverter::getPitchSSGSaw(Note note, int octave, int pitch) { return centTableSSGSaw_[calculateIndex(octave, note, pitch)]; } const uint16_t PitchConverter::centTableFM_[3072] = { 0x026a, 0x026b, 0x026c, 0x026e, 0x026f, 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0276, 0x0277, 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027e, 0x027f, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0286, 0x0287, 0x0288, 0x0289, 0x028a, 0x028b, 0x028d, 0x028e, 0x028f, 0x0290, 0x0291, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029f, 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a5, 0x02a6, 0x02a7, 0x02a8, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02b0, 0x02b1, 0x02b2, 0x02b3, 0x02b5, 0x02b6, 0x02b7, 0x02b8, 0x02ba, 0x02bb, 0x02bc, 0x02be, 0x02bf, 0x02c0, 0x02c1, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c8, 0x02c9, 0x02ca, 0x02cc, 0x02cd, 0x02ce, 0x02cf, 0x02d1, 0x02d2, 0x02d3, 0x02d5, 0x02d6, 0x02d7, 0x02d9, 0x02da, 0x02db, 0x02dd, 0x02de, 0x02df, 0x02e1, 0x02e2, 0x02e3, 0x02e5, 0x02e6, 0x02e7, 0x02e9, 0x02ea, 0x02eb, 0x02ed, 0x02ee, 0x02ef, 0x02f1, 0x02f2, 0x02f3, 0x02f5, 0x02f6, 0x02f7, 0x02f9, 0x02fa, 0x02fc, 0x02fd, 0x02fe, 0x0300, 0x0301, 0x0303, 0x0304, 0x0305, 0x0307, 0x0308, 0x030a, 0x030b, 0x030c, 0x030e, 0x030f, 0x0311, 0x0312, 0x0313, 0x0315, 0x0316, 0x0318, 0x0319, 0x031b, 0x031c, 0x031d, 0x031f, 0x0320, 0x0322, 0x0323, 0x0325, 0x0326, 0x0328, 0x0329, 0x032a, 0x032c, 0x032d, 0x032f, 0x0330, 0x0332, 0x0333, 0x0335, 0x0336, 0x0338, 0x0339, 0x033b, 0x033c, 0x033e, 0x033f, 0x0341, 0x0342, 0x0344, 0x0345, 0x0347, 0x0348, 0x034a, 0x034b, 0x034d, 0x034e, 0x0350, 0x0351, 0x0353, 0x0355, 0x0356, 0x0358, 0x0359, 0x035b, 0x035c, 0x035e, 0x035f, 0x0361, 0x0362, 0x0364, 0x0366, 0x0367, 0x0369, 0x036a, 0x036c, 0x036d, 0x036f, 0x0371, 0x0372, 0x0374, 0x0375, 0x0377, 0x0379, 0x037a, 0x037c, 0x037d, 0x037f, 0x0381, 0x0382, 0x0384, 0x0386, 0x0387, 0x0389, 0x038a, 0x038c, 0x038e, 0x038f, 0x0391, 0x0393, 0x0394, 0x0396, 0x0398, 0x0399, 0x039b, 0x039d, 0x039e, 0x03a0, 0x03a2, 0x03a3, 0x03a5, 0x03a7, 0x03a8, 0x03aa, 0x03ac, 0x03ad, 0x03af, 0x03b1, 0x03b3, 0x03b4, 0x03b6, 0x03b8, 0x03b9, 0x03bb, 0x03bd, 0x03bf, 0x03c0, 0x03c2, 0x03c4, 0x03c6, 0x03c7, 0x03c9, 0x03cb, 0x03cd, 0x03ce, 0x03d0, 0x03d2, 0x03d4, 0x03d5, 0x03d7, 0x03d9, 0x03db, 0x03dd, 0x03de, 0x03e0, 0x03e2, 0x03e4, 0x03e5, 0x03e7, 0x03e9, 0x03eb, 0x03ed, 0x03ef, 0x03f0, 0x03f2, 0x03f4, 0x03f6, 0x03f8, 0x03f9, 0x03fb, 0x03fd, 0x03ff, 0x0401, 0x0403, 0x0405, 0x0406, 0x0408, 0x040a, 0x040c, 0x040e, 0x0410, 0x0412, 0x0414, 0x0415, 0x0417, 0x0419, 0x041b, 0x041d, 0x041f, 0x0421, 0x0423, 0x0425, 0x0427, 0x0428, 0x042a, 0x042c, 0x042e, 0x0430, 0x0432, 0x0434, 0x0436, 0x0438, 0x043a, 0x043c, 0x043e, 0x0440, 0x0442, 0x0444, 0x0446, 0x0448, 0x044a, 0x044c, 0x044e, 0x0450, 0x0452, 0x0454, 0x0456, 0x0458, 0x045a, 0x045c, 0x045e, 0x0460, 0x0462, 0x0464, 0x0466, 0x0468, 0x046a, 0x046c, 0x046e, 0x0470, 0x0472, 0x0474, 0x0476, 0x0478, 0x047a, 0x047c, 0x047e, 0x0480, 0x0483, 0x0485, 0x0487, 0x0489, 0x048b, 0x048d, 0x048f, 0x0491, 0x0493, 0x0495, 0x0498, 0x049a, 0x049c, 0x049e, 0x04a0, 0x04a2, 0x04a4, 0x04a6, 0x04a9, 0x04ab, 0x04ad, 0x04af, 0x04b1, 0x04b3, 0x04b6, 0x04b8, 0x04ba, 0x04bc, 0x04be, 0x04c1, 0x04c3, 0x04c5, 0x04c7, 0x04c9, 0x04cc, 0x04ce, 0x04d0, 0x04d2, 0x04d4, 0x04d7, 0x04d9, 0x04db, 0x04dd, 0x04e0, 0x04e2, 0x04e4, 0x04e6, 0x04e9, 0x04eb, 0x04ed, 0x04f0, 0x04f2, 0x04f4, 0x04f6, 0x04f9, 0x04fb, 0x04fd, 0x0500, 0x0502, 0x0504, 0x0507, 0x0509, 0x050b, 0x050e, 0x0510, 0x0512, 0x0515, 0x0517, 0x0519, 0x051c, 0x051e, 0x0520, 0x0523, 0x0525, 0x0528, 0x052a, 0x052c, 0x052f, 0x0531, 0x0533, 0x0536, 0x0538, 0x053b, 0x053d, 0x0540, 0x0542, 0x0544, 0x0547, 0x0549, 0x054c, 0x054e, 0x0551, 0x0553, 0x0556, 0x0558, 0x055a, 0x055d, 0x055f, 0x0562, 0x0564, 0x0567, 0x0569, 0x056c, 0x056e, 0x0571, 0x0573, 0x0576, 0x0578, 0x057b, 0x057e, 0x0580, 0x0583, 0x0585, 0x0588, 0x058a, 0x058d, 0x058f, 0x0592, 0x0595, 0x0597, 0x059a, 0x059c, 0x059f, 0x05a2, 0x05a4, 0x05a7, 0x05a9, 0x05ac, 0x05af, 0x05b1, 0x05b4, 0x05b6, 0x05b9, 0x05bc, 0x05be, 0x05c1, 0x05c4, 0x05c6, 0x05c9, 0x05cc, 0x05ce, 0x05d1, 0x05d4, 0x05d7, 0x05d9, 0x05dc, 0x05df, 0x05e1, 0x05e4, 0x05e7, 0x05ea, 0x05ec, 0x05ef, 0x05f2, 0x05f4, 0x05f7, 0x05fa, 0x05fd, 0x0600, 0x0602, 0x0605, 0x0608, 0x060b, 0x060d, 0x0610, 0x0613, 0x0616, 0x0619, 0x061c, 0x061e, 0x0621, 0x0624, 0x0627, 0x062a, 0x062d, 0x062f, 0x0632, 0x0635, 0x0638, 0x063b, 0x063e, 0x0641, 0x0644, 0x0646, 0x0649, 0x064c, 0x064f, 0x0652, 0x0655, 0x0658, 0x065b, 0x065e, 0x0661, 0x0664, 0x0667, 0x066a, 0x066d, 0x0670, 0x0673, 0x0675, 0x0678, 0x067b, 0x067e, 0x0681, 0x0684, 0x0687, 0x068b, 0x068e, 0x0691, 0x0694, 0x0697, 0x069a, 0x069d, 0x06a0, 0x06a3, 0x06a6, 0x06a9, 0x06ac, 0x06af, 0x06b2, 0x06b5, 0x06b8, 0x06bc, 0x06bf, 0x06c2, 0x06c5, 0x06c8, 0x06cb, 0x06ce, 0x06d1, 0x06d5, 0x06d8, 0x06db, 0x06de, 0x06e1, 0x06e5, 0x06e8, 0x06eb, 0x06ee, 0x06f1, 0x06f5, 0x06f8, 0x06fb, 0x06fe, 0x0701, 0x0705, 0x0708, 0x070b, 0x070e, 0x0712, 0x0715, 0x0718, 0x071b, 0x071f, 0x0722, 0x0725, 0x0729, 0x072c, 0x072f, 0x0733, 0x0736, 0x0739, 0x073d, 0x0740, 0x0743, 0x0747, 0x074a, 0x074d, 0x0751, 0x0754, 0x0758, 0x075b, 0x075e, 0x0762, 0x0765, 0x0769, 0x076c, 0x076f, 0x0773, 0x0776, 0x077a, 0x077d, 0x0781, 0x0784, 0x0788, 0x078b, 0x078f, 0x0792, 0x0796, 0x0799, 0x079d, 0x07a0, 0x07a4, 0x07a7, 0x07ab, 0x07ae, 0x07b2, 0x07b5, 0x07b9, 0x07bd, 0x07c0, 0x07c4, 0x07c7, 0x07cb, 0x07cf, 0x07d2, 0x07d6, 0x07d9, 0x07dd, 0x07e1, 0x07e4, 0x07e8, 0x07ec, 0x07ef, 0x07f3, 0x07f7, 0x07fa, 0x07fe, 0x0c01, 0x0c03, 0x0c05, 0x0c06, 0x0c08, 0x0c0a, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c14, 0x0c15, 0x0c17, 0x0c19, 0x0c1b, 0x0c1d, 0x0c1f, 0x0c21, 0x0c23, 0x0c25, 0x0c27, 0x0c28, 0x0c2a, 0x0c2c, 0x0c2e, 0x0c30, 0x0c32, 0x0c34, 0x0c36, 0x0c38, 0x0c3a, 0x0c3c, 0x0c3e, 0x0c40, 0x0c42, 0x0c44, 0x0c46, 0x0c48, 0x0c4a, 0x0c4c, 0x0c4e, 0x0c50, 0x0c52, 0x0c54, 0x0c56, 0x0c58, 0x0c5a, 0x0c5c, 0x0c5e, 0x0c60, 0x0c62, 0x0c64, 0x0c66, 0x0c68, 0x0c6a, 0x0c6c, 0x0c6e, 0x0c70, 0x0c72, 0x0c74, 0x0c76, 0x0c78, 0x0c7a, 0x0c7c, 0x0c7e, 0x0c80, 0x0c83, 0x0c85, 0x0c87, 0x0c89, 0x0c8b, 0x0c8d, 0x0c8f, 0x0c91, 0x0c93, 0x0c95, 0x0c98, 0x0c9a, 0x0c9c, 0x0c9e, 0x0ca0, 0x0ca2, 0x0ca4, 0x0ca6, 0x0ca9, 0x0cab, 0x0cad, 0x0caf, 0x0cb1, 0x0cb3, 0x0cb6, 0x0cb8, 0x0cba, 0x0cbc, 0x0cbe, 0x0cc1, 0x0cc3, 0x0cc5, 0x0cc7, 0x0cc9, 0x0ccc, 0x0cce, 0x0cd0, 0x0cd2, 0x0cd4, 0x0cd7, 0x0cd9, 0x0cdb, 0x0cdd, 0x0ce0, 0x0ce2, 0x0ce4, 0x0ce6, 0x0ce9, 0x0ceb, 0x0ced, 0x0cf0, 0x0cf2, 0x0cf4, 0x0cf6, 0x0cf9, 0x0cfb, 0x0cfd, 0x0d00, 0x0d02, 0x0d04, 0x0d07, 0x0d09, 0x0d0b, 0x0d0e, 0x0d10, 0x0d12, 0x0d15, 0x0d17, 0x0d19, 0x0d1c, 0x0d1e, 0x0d20, 0x0d23, 0x0d25, 0x0d28, 0x0d2a, 0x0d2c, 0x0d2f, 0x0d31, 0x0d33, 0x0d36, 0x0d38, 0x0d3b, 0x0d3d, 0x0d40, 0x0d42, 0x0d44, 0x0d47, 0x0d49, 0x0d4c, 0x0d4e, 0x0d51, 0x0d53, 0x0d56, 0x0d58, 0x0d5a, 0x0d5d, 0x0d5f, 0x0d62, 0x0d64, 0x0d67, 0x0d69, 0x0d6c, 0x0d6e, 0x0d71, 0x0d73, 0x0d76, 0x0d78, 0x0d7b, 0x0d7e, 0x0d80, 0x0d83, 0x0d85, 0x0d88, 0x0d8a, 0x0d8d, 0x0d8f, 0x0d92, 0x0d95, 0x0d97, 0x0d9a, 0x0d9c, 0x0d9f, 0x0da2, 0x0da4, 0x0da7, 0x0da9, 0x0dac, 0x0daf, 0x0db1, 0x0db4, 0x0db6, 0x0db9, 0x0dbc, 0x0dbe, 0x0dc1, 0x0dc4, 0x0dc6, 0x0dc9, 0x0dcc, 0x0dce, 0x0dd1, 0x0dd4, 0x0dd7, 0x0dd9, 0x0ddc, 0x0ddf, 0x0de1, 0x0de4, 0x0de7, 0x0dea, 0x0dec, 0x0def, 0x0df2, 0x0df4, 0x0df7, 0x0dfa, 0x0dfd, 0x0e00, 0x0e02, 0x0e05, 0x0e08, 0x0e0b, 0x0e0d, 0x0e10, 0x0e13, 0x0e16, 0x0e19, 0x0e1c, 0x0e1e, 0x0e21, 0x0e24, 0x0e27, 0x0e2a, 0x0e2d, 0x0e2f, 0x0e32, 0x0e35, 0x0e38, 0x0e3b, 0x0e3e, 0x0e41, 0x0e44, 0x0e46, 0x0e49, 0x0e4c, 0x0e4f, 0x0e52, 0x0e55, 0x0e58, 0x0e5b, 0x0e5e, 0x0e61, 0x0e64, 0x0e67, 0x0e6a, 0x0e6d, 0x0e70, 0x0e73, 0x0e75, 0x0e78, 0x0e7b, 0x0e7e, 0x0e81, 0x0e84, 0x0e87, 0x0e8b, 0x0e8e, 0x0e91, 0x0e94, 0x0e97, 0x0e9a, 0x0e9d, 0x0ea0, 0x0ea3, 0x0ea6, 0x0ea9, 0x0eac, 0x0eaf, 0x0eb2, 0x0eb5, 0x0eb8, 0x0ebc, 0x0ebf, 0x0ec2, 0x0ec5, 0x0ec8, 0x0ecb, 0x0ece, 0x0ed1, 0x0ed5, 0x0ed8, 0x0edb, 0x0ede, 0x0ee1, 0x0ee5, 0x0ee8, 0x0eeb, 0x0eee, 0x0ef1, 0x0ef5, 0x0ef8, 0x0efb, 0x0efe, 0x0f01, 0x0f05, 0x0f08, 0x0f0b, 0x0f0e, 0x0f12, 0x0f15, 0x0f18, 0x0f1b, 0x0f1f, 0x0f22, 0x0f25, 0x0f29, 0x0f2c, 0x0f2f, 0x0f33, 0x0f36, 0x0f39, 0x0f3d, 0x0f40, 0x0f43, 0x0f47, 0x0f4a, 0x0f4d, 0x0f51, 0x0f54, 0x0f58, 0x0f5b, 0x0f5e, 0x0f62, 0x0f65, 0x0f69, 0x0f6c, 0x0f6f, 0x0f73, 0x0f76, 0x0f7a, 0x0f7d, 0x0f81, 0x0f84, 0x0f88, 0x0f8b, 0x0f8f, 0x0f92, 0x0f96, 0x0f99, 0x0f9d, 0x0fa0, 0x0fa4, 0x0fa7, 0x0fab, 0x0fae, 0x0fb2, 0x0fb5, 0x0fb9, 0x0fbd, 0x0fc0, 0x0fc4, 0x0fc7, 0x0fcb, 0x0fcf, 0x0fd2, 0x0fd6, 0x0fd9, 0x0fdd, 0x0fe1, 0x0fe4, 0x0fe8, 0x0fec, 0x0fef, 0x0ff3, 0x0ff7, 0x0ffa, 0x0ffe, 0x1401, 0x1403, 0x1405, 0x1406, 0x1408, 0x140a, 0x140c, 0x140e, 0x1410, 0x1412, 0x1414, 0x1415, 0x1417, 0x1419, 0x141b, 0x141d, 0x141f, 0x1421, 0x1423, 0x1425, 0x1427, 0x1428, 0x142a, 0x142c, 0x142e, 0x1430, 0x1432, 0x1434, 0x1436, 0x1438, 0x143a, 0x143c, 0x143e, 0x1440, 0x1442, 0x1444, 0x1446, 0x1448, 0x144a, 0x144c, 0x144e, 0x1450, 0x1452, 0x1454, 0x1456, 0x1458, 0x145a, 0x145c, 0x145e, 0x1460, 0x1462, 0x1464, 0x1466, 0x1468, 0x146a, 0x146c, 0x146e, 0x1470, 0x1472, 0x1474, 0x1476, 0x1478, 0x147a, 0x147c, 0x147e, 0x1480, 0x1483, 0x1485, 0x1487, 0x1489, 0x148b, 0x148d, 0x148f, 0x1491, 0x1493, 0x1495, 0x1498, 0x149a, 0x149c, 0x149e, 0x14a0, 0x14a2, 0x14a4, 0x14a6, 0x14a9, 0x14ab, 0x14ad, 0x14af, 0x14b1, 0x14b3, 0x14b6, 0x14b8, 0x14ba, 0x14bc, 0x14be, 0x14c1, 0x14c3, 0x14c5, 0x14c7, 0x14c9, 0x14cc, 0x14ce, 0x14d0, 0x14d2, 0x14d4, 0x14d7, 0x14d9, 0x14db, 0x14dd, 0x14e0, 0x14e2, 0x14e4, 0x14e6, 0x14e9, 0x14eb, 0x14ed, 0x14f0, 0x14f2, 0x14f4, 0x14f6, 0x14f9, 0x14fb, 0x14fd, 0x1500, 0x1502, 0x1504, 0x1507, 0x1509, 0x150b, 0x150e, 0x1510, 0x1512, 0x1515, 0x1517, 0x1519, 0x151c, 0x151e, 0x1520, 0x1523, 0x1525, 0x1528, 0x152a, 0x152c, 0x152f, 0x1531, 0x1533, 0x1536, 0x1538, 0x153b, 0x153d, 0x1540, 0x1542, 0x1544, 0x1547, 0x1549, 0x154c, 0x154e, 0x1551, 0x1553, 0x1556, 0x1558, 0x155a, 0x155d, 0x155f, 0x1562, 0x1564, 0x1567, 0x1569, 0x156c, 0x156e, 0x1571, 0x1573, 0x1576, 0x1578, 0x157b, 0x157e, 0x1580, 0x1583, 0x1585, 0x1588, 0x158a, 0x158d, 0x158f, 0x1592, 0x1595, 0x1597, 0x159a, 0x159c, 0x159f, 0x15a2, 0x15a4, 0x15a7, 0x15a9, 0x15ac, 0x15af, 0x15b1, 0x15b4, 0x15b6, 0x15b9, 0x15bc, 0x15be, 0x15c1, 0x15c4, 0x15c6, 0x15c9, 0x15cc, 0x15ce, 0x15d1, 0x15d4, 0x15d7, 0x15d9, 0x15dc, 0x15df, 0x15e1, 0x15e4, 0x15e7, 0x15ea, 0x15ec, 0x15ef, 0x15f2, 0x15f4, 0x15f7, 0x15fa, 0x15fd, 0x1600, 0x1602, 0x1605, 0x1608, 0x160b, 0x160d, 0x1610, 0x1613, 0x1616, 0x1619, 0x161c, 0x161e, 0x1621, 0x1624, 0x1627, 0x162a, 0x162d, 0x162f, 0x1632, 0x1635, 0x1638, 0x163b, 0x163e, 0x1641, 0x1644, 0x1646, 0x1649, 0x164c, 0x164f, 0x1652, 0x1655, 0x1658, 0x165b, 0x165e, 0x1661, 0x1664, 0x1667, 0x166a, 0x166d, 0x1670, 0x1673, 0x1675, 0x1678, 0x167b, 0x167e, 0x1681, 0x1684, 0x1687, 0x168b, 0x168e, 0x1691, 0x1694, 0x1697, 0x169a, 0x169d, 0x16a0, 0x16a3, 0x16a6, 0x16a9, 0x16ac, 0x16af, 0x16b2, 0x16b5, 0x16b8, 0x16bc, 0x16bf, 0x16c2, 0x16c5, 0x16c8, 0x16cb, 0x16ce, 0x16d1, 0x16d5, 0x16d8, 0x16db, 0x16de, 0x16e1, 0x16e5, 0x16e8, 0x16eb, 0x16ee, 0x16f1, 0x16f5, 0x16f8, 0x16fb, 0x16fe, 0x1701, 0x1705, 0x1708, 0x170b, 0x170e, 0x1712, 0x1715, 0x1718, 0x171b, 0x171f, 0x1722, 0x1725, 0x1729, 0x172c, 0x172f, 0x1733, 0x1736, 0x1739, 0x173d, 0x1740, 0x1743, 0x1747, 0x174a, 0x174d, 0x1751, 0x1754, 0x1758, 0x175b, 0x175e, 0x1762, 0x1765, 0x1769, 0x176c, 0x176f, 0x1773, 0x1776, 0x177a, 0x177d, 0x1781, 0x1784, 0x1788, 0x178b, 0x178f, 0x1792, 0x1796, 0x1799, 0x179d, 0x17a0, 0x17a4, 0x17a7, 0x17ab, 0x17ae, 0x17b2, 0x17b5, 0x17b9, 0x17bd, 0x17c0, 0x17c4, 0x17c7, 0x17cb, 0x17cf, 0x17d2, 0x17d6, 0x17d9, 0x17dd, 0x17e1, 0x17e4, 0x17e8, 0x17ec, 0x17ef, 0x17f3, 0x17f7, 0x17fa, 0x17fe, 0x1c01, 0x1c03, 0x1c05, 0x1c06, 0x1c08, 0x1c0a, 0x1c0c, 0x1c0e, 0x1c10, 0x1c12, 0x1c14, 0x1c15, 0x1c17, 0x1c19, 0x1c1b, 0x1c1d, 0x1c1f, 0x1c21, 0x1c23, 0x1c25, 0x1c27, 0x1c28, 0x1c2a, 0x1c2c, 0x1c2e, 0x1c30, 0x1c32, 0x1c34, 0x1c36, 0x1c38, 0x1c3a, 0x1c3c, 0x1c3e, 0x1c40, 0x1c42, 0x1c44, 0x1c46, 0x1c48, 0x1c4a, 0x1c4c, 0x1c4e, 0x1c50, 0x1c52, 0x1c54, 0x1c56, 0x1c58, 0x1c5a, 0x1c5c, 0x1c5e, 0x1c60, 0x1c62, 0x1c64, 0x1c66, 0x1c68, 0x1c6a, 0x1c6c, 0x1c6e, 0x1c70, 0x1c72, 0x1c74, 0x1c76, 0x1c78, 0x1c7a, 0x1c7c, 0x1c7e, 0x1c80, 0x1c83, 0x1c85, 0x1c87, 0x1c89, 0x1c8b, 0x1c8d, 0x1c8f, 0x1c91, 0x1c93, 0x1c95, 0x1c98, 0x1c9a, 0x1c9c, 0x1c9e, 0x1ca0, 0x1ca2, 0x1ca4, 0x1ca6, 0x1ca9, 0x1cab, 0x1cad, 0x1caf, 0x1cb1, 0x1cb3, 0x1cb6, 0x1cb8, 0x1cba, 0x1cbc, 0x1cbe, 0x1cc1, 0x1cc3, 0x1cc5, 0x1cc7, 0x1cc9, 0x1ccc, 0x1cce, 0x1cd0, 0x1cd2, 0x1cd4, 0x1cd7, 0x1cd9, 0x1cdb, 0x1cdd, 0x1ce0, 0x1ce2, 0x1ce4, 0x1ce6, 0x1ce9, 0x1ceb, 0x1ced, 0x1cf0, 0x1cf2, 0x1cf4, 0x1cf6, 0x1cf9, 0x1cfb, 0x1cfd, 0x1d00, 0x1d02, 0x1d04, 0x1d07, 0x1d09, 0x1d0b, 0x1d0e, 0x1d10, 0x1d12, 0x1d15, 0x1d17, 0x1d19, 0x1d1c, 0x1d1e, 0x1d20, 0x1d23, 0x1d25, 0x1d28, 0x1d2a, 0x1d2c, 0x1d2f, 0x1d31, 0x1d33, 0x1d36, 0x1d38, 0x1d3b, 0x1d3d, 0x1d40, 0x1d42, 0x1d44, 0x1d47, 0x1d49, 0x1d4c, 0x1d4e, 0x1d51, 0x1d53, 0x1d56, 0x1d58, 0x1d5a, 0x1d5d, 0x1d5f, 0x1d62, 0x1d64, 0x1d67, 0x1d69, 0x1d6c, 0x1d6e, 0x1d71, 0x1d73, 0x1d76, 0x1d78, 0x1d7b, 0x1d7e, 0x1d80, 0x1d83, 0x1d85, 0x1d88, 0x1d8a, 0x1d8d, 0x1d8f, 0x1d92, 0x1d95, 0x1d97, 0x1d9a, 0x1d9c, 0x1d9f, 0x1da2, 0x1da4, 0x1da7, 0x1da9, 0x1dac, 0x1daf, 0x1db1, 0x1db4, 0x1db6, 0x1db9, 0x1dbc, 0x1dbe, 0x1dc1, 0x1dc4, 0x1dc6, 0x1dc9, 0x1dcc, 0x1dce, 0x1dd1, 0x1dd4, 0x1dd7, 0x1dd9, 0x1ddc, 0x1ddf, 0x1de1, 0x1de4, 0x1de7, 0x1dea, 0x1dec, 0x1def, 0x1df2, 0x1df4, 0x1df7, 0x1dfa, 0x1dfd, 0x1e00, 0x1e02, 0x1e05, 0x1e08, 0x1e0b, 0x1e0d, 0x1e10, 0x1e13, 0x1e16, 0x1e19, 0x1e1c, 0x1e1e, 0x1e21, 0x1e24, 0x1e27, 0x1e2a, 0x1e2d, 0x1e2f, 0x1e32, 0x1e35, 0x1e38, 0x1e3b, 0x1e3e, 0x1e41, 0x1e44, 0x1e46, 0x1e49, 0x1e4c, 0x1e4f, 0x1e52, 0x1e55, 0x1e58, 0x1e5b, 0x1e5e, 0x1e61, 0x1e64, 0x1e67, 0x1e6a, 0x1e6d, 0x1e70, 0x1e73, 0x1e75, 0x1e78, 0x1e7b, 0x1e7e, 0x1e81, 0x1e84, 0x1e87, 0x1e8b, 0x1e8e, 0x1e91, 0x1e94, 0x1e97, 0x1e9a, 0x1e9d, 0x1ea0, 0x1ea3, 0x1ea6, 0x1ea9, 0x1eac, 0x1eaf, 0x1eb2, 0x1eb5, 0x1eb8, 0x1ebc, 0x1ebf, 0x1ec2, 0x1ec5, 0x1ec8, 0x1ecb, 0x1ece, 0x1ed1, 0x1ed5, 0x1ed8, 0x1edb, 0x1ede, 0x1ee1, 0x1ee5, 0x1ee8, 0x1eeb, 0x1eee, 0x1ef1, 0x1ef5, 0x1ef8, 0x1efb, 0x1efe, 0x1f01, 0x1f05, 0x1f08, 0x1f0b, 0x1f0e, 0x1f12, 0x1f15, 0x1f18, 0x1f1b, 0x1f1f, 0x1f22, 0x1f25, 0x1f29, 0x1f2c, 0x1f2f, 0x1f33, 0x1f36, 0x1f39, 0x1f3d, 0x1f40, 0x1f43, 0x1f47, 0x1f4a, 0x1f4d, 0x1f51, 0x1f54, 0x1f58, 0x1f5b, 0x1f5e, 0x1f62, 0x1f65, 0x1f69, 0x1f6c, 0x1f6f, 0x1f73, 0x1f76, 0x1f7a, 0x1f7d, 0x1f81, 0x1f84, 0x1f88, 0x1f8b, 0x1f8f, 0x1f92, 0x1f96, 0x1f99, 0x1f9d, 0x1fa0, 0x1fa4, 0x1fa7, 0x1fab, 0x1fae, 0x1fb2, 0x1fb5, 0x1fb9, 0x1fbd, 0x1fc0, 0x1fc4, 0x1fc7, 0x1fcb, 0x1fcf, 0x1fd2, 0x1fd6, 0x1fd9, 0x1fdd, 0x1fe1, 0x1fe4, 0x1fe8, 0x1fec, 0x1fef, 0x1ff3, 0x1ff7, 0x1ffa, 0x1ffe, 0x2401, 0x2403, 0x2405, 0x2406, 0x2408, 0x240a, 0x240c, 0x240e, 0x2410, 0x2412, 0x2414, 0x2415, 0x2417, 0x2419, 0x241b, 0x241d, 0x241f, 0x2421, 0x2423, 0x2425, 0x2427, 0x2428, 0x242a, 0x242c, 0x242e, 0x2430, 0x2432, 0x2434, 0x2436, 0x2438, 0x243a, 0x243c, 0x243e, 0x2440, 0x2442, 0x2444, 0x2446, 0x2448, 0x244a, 0x244c, 0x244e, 0x2450, 0x2452, 0x2454, 0x2456, 0x2458, 0x245a, 0x245c, 0x245e, 0x2460, 0x2462, 0x2464, 0x2466, 0x2468, 0x246a, 0x246c, 0x246e, 0x2470, 0x2472, 0x2474, 0x2476, 0x2478, 0x247a, 0x247c, 0x247e, 0x2480, 0x2483, 0x2485, 0x2487, 0x2489, 0x248b, 0x248d, 0x248f, 0x2491, 0x2493, 0x2495, 0x2498, 0x249a, 0x249c, 0x249e, 0x24a0, 0x24a2, 0x24a4, 0x24a6, 0x24a9, 0x24ab, 0x24ad, 0x24af, 0x24b1, 0x24b3, 0x24b6, 0x24b8, 0x24ba, 0x24bc, 0x24be, 0x24c1, 0x24c3, 0x24c5, 0x24c7, 0x24c9, 0x24cc, 0x24ce, 0x24d0, 0x24d2, 0x24d4, 0x24d7, 0x24d9, 0x24db, 0x24dd, 0x24e0, 0x24e2, 0x24e4, 0x24e6, 0x24e9, 0x24eb, 0x24ed, 0x24f0, 0x24f2, 0x24f4, 0x24f6, 0x24f9, 0x24fb, 0x24fd, 0x2500, 0x2502, 0x2504, 0x2507, 0x2509, 0x250b, 0x250e, 0x2510, 0x2512, 0x2515, 0x2517, 0x2519, 0x251c, 0x251e, 0x2520, 0x2523, 0x2525, 0x2528, 0x252a, 0x252c, 0x252f, 0x2531, 0x2533, 0x2536, 0x2538, 0x253b, 0x253d, 0x2540, 0x2542, 0x2544, 0x2547, 0x2549, 0x254c, 0x254e, 0x2551, 0x2553, 0x2556, 0x2558, 0x255a, 0x255d, 0x255f, 0x2562, 0x2564, 0x2567, 0x2569, 0x256c, 0x256e, 0x2571, 0x2573, 0x2576, 0x2578, 0x257b, 0x257e, 0x2580, 0x2583, 0x2585, 0x2588, 0x258a, 0x258d, 0x258f, 0x2592, 0x2595, 0x2597, 0x259a, 0x259c, 0x259f, 0x25a2, 0x25a4, 0x25a7, 0x25a9, 0x25ac, 0x25af, 0x25b1, 0x25b4, 0x25b6, 0x25b9, 0x25bc, 0x25be, 0x25c1, 0x25c4, 0x25c6, 0x25c9, 0x25cc, 0x25ce, 0x25d1, 0x25d4, 0x25d7, 0x25d9, 0x25dc, 0x25df, 0x25e1, 0x25e4, 0x25e7, 0x25ea, 0x25ec, 0x25ef, 0x25f2, 0x25f4, 0x25f7, 0x25fa, 0x25fd, 0x2600, 0x2602, 0x2605, 0x2608, 0x260b, 0x260d, 0x2610, 0x2613, 0x2616, 0x2619, 0x261c, 0x261e, 0x2621, 0x2624, 0x2627, 0x262a, 0x262d, 0x262f, 0x2632, 0x2635, 0x2638, 0x263b, 0x263e, 0x2641, 0x2644, 0x2646, 0x2649, 0x264c, 0x264f, 0x2652, 0x2655, 0x2658, 0x265b, 0x265e, 0x2661, 0x2664, 0x2667, 0x266a, 0x266d, 0x2670, 0x2673, 0x2675, 0x2678, 0x267b, 0x267e, 0x2681, 0x2684, 0x2687, 0x268b, 0x268e, 0x2691, 0x2694, 0x2697, 0x269a, 0x269d, 0x26a0, 0x26a3, 0x26a6, 0x26a9, 0x26ac, 0x26af, 0x26b2, 0x26b5, 0x26b8, 0x26bc, 0x26bf, 0x26c2, 0x26c5, 0x26c8, 0x26cb, 0x26ce, 0x26d1, 0x26d5, 0x26d8, 0x26db, 0x26de, 0x26e1, 0x26e5, 0x26e8, 0x26eb, 0x26ee, 0x26f1, 0x26f5, 0x26f8, 0x26fb, 0x26fe, 0x2701, 0x2705, 0x2708, 0x270b, 0x270e, 0x2712, 0x2715, 0x2718, 0x271b, 0x271f, 0x2722, 0x2725, 0x2729, 0x272c, 0x272f, 0x2733, 0x2736, 0x2739, 0x273d, 0x2740, 0x2743, 0x2747, 0x274a, 0x274d, 0x2751, 0x2754, 0x2758, 0x275b, 0x275e, 0x2762, 0x2765, 0x2769, 0x276c, 0x276f, 0x2773, 0x2776, 0x277a, 0x277d, 0x2781, 0x2784, 0x2788, 0x278b, 0x278f, 0x2792, 0x2796, 0x2799, 0x279d, 0x27a0, 0x27a4, 0x27a7, 0x27ab, 0x27ae, 0x27b2, 0x27b5, 0x27b9, 0x27bd, 0x27c0, 0x27c4, 0x27c7, 0x27cb, 0x27cf, 0x27d2, 0x27d6, 0x27d9, 0x27dd, 0x27e1, 0x27e4, 0x27e8, 0x27ec, 0x27ef, 0x27f3, 0x27f7, 0x27fa, 0x27fe, 0x2c01, 0x2c03, 0x2c05, 0x2c06, 0x2c08, 0x2c0a, 0x2c0c, 0x2c0e, 0x2c10, 0x2c12, 0x2c14, 0x2c15, 0x2c17, 0x2c19, 0x2c1b, 0x2c1d, 0x2c1f, 0x2c21, 0x2c23, 0x2c25, 0x2c27, 0x2c28, 0x2c2a, 0x2c2c, 0x2c2e, 0x2c30, 0x2c32, 0x2c34, 0x2c36, 0x2c38, 0x2c3a, 0x2c3c, 0x2c3e, 0x2c40, 0x2c42, 0x2c44, 0x2c46, 0x2c48, 0x2c4a, 0x2c4c, 0x2c4e, 0x2c50, 0x2c52, 0x2c54, 0x2c56, 0x2c58, 0x2c5a, 0x2c5c, 0x2c5e, 0x2c60, 0x2c62, 0x2c64, 0x2c66, 0x2c68, 0x2c6a, 0x2c6c, 0x2c6e, 0x2c70, 0x2c72, 0x2c74, 0x2c76, 0x2c78, 0x2c7a, 0x2c7c, 0x2c7e, 0x2c80, 0x2c83, 0x2c85, 0x2c87, 0x2c89, 0x2c8b, 0x2c8d, 0x2c8f, 0x2c91, 0x2c93, 0x2c95, 0x2c98, 0x2c9a, 0x2c9c, 0x2c9e, 0x2ca0, 0x2ca2, 0x2ca4, 0x2ca6, 0x2ca9, 0x2cab, 0x2cad, 0x2caf, 0x2cb1, 0x2cb3, 0x2cb6, 0x2cb8, 0x2cba, 0x2cbc, 0x2cbe, 0x2cc1, 0x2cc3, 0x2cc5, 0x2cc7, 0x2cc9, 0x2ccc, 0x2cce, 0x2cd0, 0x2cd2, 0x2cd4, 0x2cd7, 0x2cd9, 0x2cdb, 0x2cdd, 0x2ce0, 0x2ce2, 0x2ce4, 0x2ce6, 0x2ce9, 0x2ceb, 0x2ced, 0x2cf0, 0x2cf2, 0x2cf4, 0x2cf6, 0x2cf9, 0x2cfb, 0x2cfd, 0x2d00, 0x2d02, 0x2d04, 0x2d07, 0x2d09, 0x2d0b, 0x2d0e, 0x2d10, 0x2d12, 0x2d15, 0x2d17, 0x2d19, 0x2d1c, 0x2d1e, 0x2d20, 0x2d23, 0x2d25, 0x2d28, 0x2d2a, 0x2d2c, 0x2d2f, 0x2d31, 0x2d33, 0x2d36, 0x2d38, 0x2d3b, 0x2d3d, 0x2d40, 0x2d42, 0x2d44, 0x2d47, 0x2d49, 0x2d4c, 0x2d4e, 0x2d51, 0x2d53, 0x2d56, 0x2d58, 0x2d5a, 0x2d5d, 0x2d5f, 0x2d62, 0x2d64, 0x2d67, 0x2d69, 0x2d6c, 0x2d6e, 0x2d71, 0x2d73, 0x2d76, 0x2d78, 0x2d7b, 0x2d7e, 0x2d80, 0x2d83, 0x2d85, 0x2d88, 0x2d8a, 0x2d8d, 0x2d8f, 0x2d92, 0x2d95, 0x2d97, 0x2d9a, 0x2d9c, 0x2d9f, 0x2da2, 0x2da4, 0x2da7, 0x2da9, 0x2dac, 0x2daf, 0x2db1, 0x2db4, 0x2db6, 0x2db9, 0x2dbc, 0x2dbe, 0x2dc1, 0x2dc4, 0x2dc6, 0x2dc9, 0x2dcc, 0x2dce, 0x2dd1, 0x2dd4, 0x2dd7, 0x2dd9, 0x2ddc, 0x2ddf, 0x2de1, 0x2de4, 0x2de7, 0x2dea, 0x2dec, 0x2def, 0x2df2, 0x2df4, 0x2df7, 0x2dfa, 0x2dfd, 0x2e00, 0x2e02, 0x2e05, 0x2e08, 0x2e0b, 0x2e0d, 0x2e10, 0x2e13, 0x2e16, 0x2e19, 0x2e1c, 0x2e1e, 0x2e21, 0x2e24, 0x2e27, 0x2e2a, 0x2e2d, 0x2e2f, 0x2e32, 0x2e35, 0x2e38, 0x2e3b, 0x2e3e, 0x2e41, 0x2e44, 0x2e46, 0x2e49, 0x2e4c, 0x2e4f, 0x2e52, 0x2e55, 0x2e58, 0x2e5b, 0x2e5e, 0x2e61, 0x2e64, 0x2e67, 0x2e6a, 0x2e6d, 0x2e70, 0x2e73, 0x2e75, 0x2e78, 0x2e7b, 0x2e7e, 0x2e81, 0x2e84, 0x2e87, 0x2e8b, 0x2e8e, 0x2e91, 0x2e94, 0x2e97, 0x2e9a, 0x2e9d, 0x2ea0, 0x2ea3, 0x2ea6, 0x2ea9, 0x2eac, 0x2eaf, 0x2eb2, 0x2eb5, 0x2eb8, 0x2ebc, 0x2ebf, 0x2ec2, 0x2ec5, 0x2ec8, 0x2ecb, 0x2ece, 0x2ed1, 0x2ed5, 0x2ed8, 0x2edb, 0x2ede, 0x2ee1, 0x2ee5, 0x2ee8, 0x2eeb, 0x2eee, 0x2ef1, 0x2ef5, 0x2ef8, 0x2efb, 0x2efe, 0x2f01, 0x2f05, 0x2f08, 0x2f0b, 0x2f0e, 0x2f12, 0x2f15, 0x2f18, 0x2f1b, 0x2f1f, 0x2f22, 0x2f25, 0x2f29, 0x2f2c, 0x2f2f, 0x2f33, 0x2f36, 0x2f39, 0x2f3d, 0x2f40, 0x2f43, 0x2f47, 0x2f4a, 0x2f4d, 0x2f51, 0x2f54, 0x2f58, 0x2f5b, 0x2f5e, 0x2f62, 0x2f65, 0x2f69, 0x2f6c, 0x2f6f, 0x2f73, 0x2f76, 0x2f7a, 0x2f7d, 0x2f81, 0x2f84, 0x2f88, 0x2f8b, 0x2f8f, 0x2f92, 0x2f96, 0x2f99, 0x2f9d, 0x2fa0, 0x2fa4, 0x2fa7, 0x2fab, 0x2fae, 0x2fb2, 0x2fb5, 0x2fb9, 0x2fbd, 0x2fc0, 0x2fc4, 0x2fc7, 0x2fcb, 0x2fcf, 0x2fd2, 0x2fd6, 0x2fd9, 0x2fdd, 0x2fe1, 0x2fe4, 0x2fe8, 0x2fec, 0x2fef, 0x2ff3, 0x2ff7, 0x2ffa, 0x2ffe, 0x3401, 0x3403, 0x3405, 0x3406, 0x3408, 0x340a, 0x340c, 0x340e, 0x3410, 0x3412, 0x3414, 0x3415, 0x3417, 0x3419, 0x341b, 0x341d, 0x341f, 0x3421, 0x3423, 0x3425, 0x3427, 0x3428, 0x342a, 0x342c, 0x342e, 0x3430, 0x3432, 0x3434, 0x3436, 0x3438, 0x343a, 0x343c, 0x343e, 0x3440, 0x3442, 0x3444, 0x3446, 0x3448, 0x344a, 0x344c, 0x344e, 0x3450, 0x3452, 0x3454, 0x3456, 0x3458, 0x345a, 0x345c, 0x345e, 0x3460, 0x3462, 0x3464, 0x3466, 0x3468, 0x346a, 0x346c, 0x346e, 0x3470, 0x3472, 0x3474, 0x3476, 0x3478, 0x347a, 0x347c, 0x347e, 0x3480, 0x3483, 0x3485, 0x3487, 0x3489, 0x348b, 0x348d, 0x348f, 0x3491, 0x3493, 0x3495, 0x3498, 0x349a, 0x349c, 0x349e, 0x34a0, 0x34a2, 0x34a4, 0x34a6, 0x34a9, 0x34ab, 0x34ad, 0x34af, 0x34b1, 0x34b3, 0x34b6, 0x34b8, 0x34ba, 0x34bc, 0x34be, 0x34c1, 0x34c3, 0x34c5, 0x34c7, 0x34c9, 0x34cc, 0x34ce, 0x34d0, 0x34d2, 0x34d4, 0x34d7, 0x34d9, 0x34db, 0x34dd, 0x34e0, 0x34e2, 0x34e4, 0x34e6, 0x34e9, 0x34eb, 0x34ed, 0x34f0, 0x34f2, 0x34f4, 0x34f6, 0x34f9, 0x34fb, 0x34fd, 0x3500, 0x3502, 0x3504, 0x3507, 0x3509, 0x350b, 0x350e, 0x3510, 0x3512, 0x3515, 0x3517, 0x3519, 0x351c, 0x351e, 0x3520, 0x3523, 0x3525, 0x3528, 0x352a, 0x352c, 0x352f, 0x3531, 0x3533, 0x3536, 0x3538, 0x353b, 0x353d, 0x3540, 0x3542, 0x3544, 0x3547, 0x3549, 0x354c, 0x354e, 0x3551, 0x3553, 0x3556, 0x3558, 0x355a, 0x355d, 0x355f, 0x3562, 0x3564, 0x3567, 0x3569, 0x356c, 0x356e, 0x3571, 0x3573, 0x3576, 0x3578, 0x357b, 0x357e, 0x3580, 0x3583, 0x3585, 0x3588, 0x358a, 0x358d, 0x358f, 0x3592, 0x3595, 0x3597, 0x359a, 0x359c, 0x359f, 0x35a2, 0x35a4, 0x35a7, 0x35a9, 0x35ac, 0x35af, 0x35b1, 0x35b4, 0x35b6, 0x35b9, 0x35bc, 0x35be, 0x35c1, 0x35c4, 0x35c6, 0x35c9, 0x35cc, 0x35ce, 0x35d1, 0x35d4, 0x35d7, 0x35d9, 0x35dc, 0x35df, 0x35e1, 0x35e4, 0x35e7, 0x35ea, 0x35ec, 0x35ef, 0x35f2, 0x35f4, 0x35f7, 0x35fa, 0x35fd, 0x3600, 0x3602, 0x3605, 0x3608, 0x360b, 0x360d, 0x3610, 0x3613, 0x3616, 0x3619, 0x361c, 0x361e, 0x3621, 0x3624, 0x3627, 0x362a, 0x362d, 0x362f, 0x3632, 0x3635, 0x3638, 0x363b, 0x363e, 0x3641, 0x3644, 0x3646, 0x3649, 0x364c, 0x364f, 0x3652, 0x3655, 0x3658, 0x365b, 0x365e, 0x3661, 0x3664, 0x3667, 0x366a, 0x366d, 0x3670, 0x3673, 0x3675, 0x3678, 0x367b, 0x367e, 0x3681, 0x3684, 0x3687, 0x368b, 0x368e, 0x3691, 0x3694, 0x3697, 0x369a, 0x369d, 0x36a0, 0x36a3, 0x36a6, 0x36a9, 0x36ac, 0x36af, 0x36b2, 0x36b5, 0x36b8, 0x36bc, 0x36bf, 0x36c2, 0x36c5, 0x36c8, 0x36cb, 0x36ce, 0x36d1, 0x36d5, 0x36d8, 0x36db, 0x36de, 0x36e1, 0x36e5, 0x36e8, 0x36eb, 0x36ee, 0x36f1, 0x36f5, 0x36f8, 0x36fb, 0x36fe, 0x3701, 0x3705, 0x3708, 0x370b, 0x370e, 0x3712, 0x3715, 0x3718, 0x371b, 0x371f, 0x3722, 0x3725, 0x3729, 0x372c, 0x372f, 0x3733, 0x3736, 0x3739, 0x373d, 0x3740, 0x3743, 0x3747, 0x374a, 0x374d, 0x3751, 0x3754, 0x3758, 0x375b, 0x375e, 0x3762, 0x3765, 0x3769, 0x376c, 0x376f, 0x3773, 0x3776, 0x377a, 0x377d, 0x3781, 0x3784, 0x3788, 0x378b, 0x378f, 0x3792, 0x3796, 0x3799, 0x379d, 0x37a0, 0x37a4, 0x37a7, 0x37ab, 0x37ae, 0x37b2, 0x37b5, 0x37b9, 0x37bd, 0x37c0, 0x37c4, 0x37c7, 0x37cb, 0x37cf, 0x37d2, 0x37d6, 0x37d9, 0x37dd, 0x37e1, 0x37e4, 0x37e8, 0x37ec, 0x37ef, 0x37f3, 0x37f7, 0x37fa, 0x37fe, 0x3c01, 0x3c03, 0x3c05, 0x3c06, 0x3c08, 0x3c0a, 0x3c0c, 0x3c0e, 0x3c10, 0x3c12, 0x3c14, 0x3c15, 0x3c17, 0x3c19, 0x3c1b, 0x3c1d, 0x3c1f, 0x3c21, 0x3c23, 0x3c25, 0x3c27, 0x3c28, 0x3c2a, 0x3c2c, 0x3c2e, 0x3c30, 0x3c32, 0x3c34, 0x3c36, 0x3c38, 0x3c3a, 0x3c3c, 0x3c3e, 0x3c40, 0x3c42, 0x3c44, 0x3c46, 0x3c48, 0x3c4a, 0x3c4c, 0x3c4e, 0x3c50, 0x3c52, 0x3c54, 0x3c56, 0x3c58, 0x3c5a, 0x3c5c, 0x3c5e, 0x3c60, 0x3c62, 0x3c64, 0x3c66, 0x3c68, 0x3c6a, 0x3c6c, 0x3c6e, 0x3c70, 0x3c72, 0x3c74, 0x3c76, 0x3c78, 0x3c7a, 0x3c7c, 0x3c7e, 0x3c80, 0x3c83, 0x3c85, 0x3c87, 0x3c89, 0x3c8b, 0x3c8d, 0x3c8f, 0x3c91, 0x3c93, 0x3c95, 0x3c98, 0x3c9a, 0x3c9c, 0x3c9e, 0x3ca0, 0x3ca2, 0x3ca4, 0x3ca6, 0x3ca9, 0x3cab, 0x3cad, 0x3caf, 0x3cb1, 0x3cb3, 0x3cb6, 0x3cb8, 0x3cba, 0x3cbc, 0x3cbe, 0x3cc1, 0x3cc3, 0x3cc5, 0x3cc7, 0x3cc9, 0x3ccc, 0x3cce, 0x3cd0, 0x3cd2 }; const uint16_t PitchConverter::centTableSSGSquare_[3072] = { 0xee8, 0xee1, 0xeda, 0xed4, 0xecd, 0xec6, 0xebf, 0xeb8, 0xeb1, 0xeab, 0xea4, 0xe9d, 0xe96, 0xe90, 0xe89, 0xe82, 0xe7c, 0xe75, 0xe6e, 0xe67, 0xe61, 0xe5a, 0xe54, 0xe4d, 0xe46, 0xe40, 0xe39, 0xe33, 0xe2c, 0xe26, 0xe1f, 0xe18, 0xe12, 0xe0b, 0xe05, 0xdff, 0xdf8, 0xdf2, 0xdeb, 0xde5, 0xdde, 0xdd8, 0xdd2, 0xdcb, 0xdc5, 0xdbe, 0xdb8, 0xdb2, 0xdab, 0xda5, 0xd9f, 0xd99, 0xd92, 0xd8c, 0xd86, 0xd7f, 0xd79, 0xd73, 0xd6d, 0xd67, 0xd60, 0xd5a, 0xd54, 0xd4e, 0xd48, 0xd42, 0xd3c, 0xd35, 0xd2f, 0xd29, 0xd23, 0xd1d, 0xd17, 0xd11, 0xd0b, 0xd05, 0xcff, 0xcf9, 0xcf3, 0xced, 0xce7, 0xce1, 0xcdb, 0xcd5, 0xccf, 0xcc9, 0xcc3, 0xcbe, 0xcb8, 0xcb2, 0xcac, 0xca6, 0xca0, 0xc9a, 0xc95, 0xc8f, 0xc89, 0xc83, 0xc7d, 0xc78, 0xc72, 0xc6c, 0xc66, 0xc61, 0xc5b, 0xc55, 0xc50, 0xc4a, 0xc44, 0xc3f, 0xc39, 0xc33, 0xc2e, 0xc28, 0xc22, 0xc1d, 0xc17, 0xc12, 0xc0c, 0xc06, 0xc01, 0xbfb, 0xbf6, 0xbf0, 0xbeb, 0xbe5, 0xbe0, 0xbda, 0xbd5, 0xbcf, 0xbca, 0xbc5, 0xbbf, 0xbba, 0xbb4, 0xbaf, 0xba9, 0xba4, 0xb9f, 0xb99, 0xb94, 0xb8f, 0xb89, 0xb84, 0xb7f, 0xb79, 0xb74, 0xb6f, 0xb69, 0xb64, 0xb5f, 0xb5a, 0xb54, 0xb4f, 0xb4a, 0xb45, 0xb40, 0xb3a, 0xb35, 0xb30, 0xb2b, 0xb26, 0xb21, 0xb1b, 0xb16, 0xb11, 0xb0c, 0xb07, 0xb02, 0xafd, 0xaf8, 0xaf3, 0xaee, 0xae9, 0xae4, 0xadf, 0xad9, 0xad4, 0xacf, 0xaca, 0xac6, 0xac1, 0xabc, 0xab7, 0xab2, 0xaad, 0xaa8, 0xaa3, 0xa9e, 0xa99, 0xa94, 0xa8f, 0xa8a, 0xa86, 0xa81, 0xa7c, 0xa77, 0xa72, 0xa6d, 0xa69, 0xa64, 0xa5f, 0xa5a, 0xa55, 0xa51, 0xa4c, 0xa47, 0xa42, 0xa3e, 0xa39, 0xa34, 0xa2f, 0xa2b, 0xa26, 0xa21, 0xa1d, 0xa18, 0xa13, 0xa0f, 0xa0a, 0xa05, 0xa01, 0x9fc, 0x9f8, 0x9f3, 0x9ee, 0x9ea, 0x9e5, 0x9e1, 0x9dc, 0x9d8, 0x9d3, 0x9ce, 0x9ca, 0x9c5, 0x9c1, 0x9bc, 0x9b8, 0x9b3, 0x9af, 0x9aa, 0x9a6, 0x9a2, 0x99d, 0x999, 0x994, 0x990, 0x98b, 0x987, 0x983, 0x97e, 0x97a, 0x975, 0x971, 0x96d, 0x968, 0x964, 0x960, 0x95b, 0x957, 0x953, 0x94e, 0x94a, 0x946, 0x942, 0x93d, 0x939, 0x935, 0x931, 0x92c, 0x928, 0x924, 0x920, 0x91b, 0x917, 0x913, 0x90f, 0x90b, 0x906, 0x902, 0x8fe, 0x8fa, 0x8f6, 0x8f2, 0x8ee, 0x8e9, 0x8e5, 0x8e1, 0x8dd, 0x8d9, 0x8d5, 0x8d1, 0x8cd, 0x8c9, 0x8c5, 0x8c1, 0x8bd, 0x8b9, 0x8b4, 0x8b0, 0x8ac, 0x8a8, 0x8a4, 0x8a0, 0x89c, 0x899, 0x895, 0x891, 0x88d, 0x889, 0x885, 0x881, 0x87d, 0x879, 0x875, 0x871, 0x86d, 0x869, 0x865, 0x862, 0x85e, 0x85a, 0x856, 0x852, 0x84e, 0x84a, 0x847, 0x843, 0x83f, 0x83b, 0x837, 0x834, 0x830, 0x82c, 0x828, 0x825, 0x821, 0x81d, 0x819, 0x816, 0x812, 0x80e, 0x80a, 0x807, 0x803, 0x7ff, 0x7fc, 0x7f8, 0x7f4, 0x7f1, 0x7ed, 0x7e9, 0x7e6, 0x7e2, 0x7de, 0x7db, 0x7d7, 0x7d3, 0x7d0, 0x7cc, 0x7c9, 0x7c5, 0x7c1, 0x7be, 0x7ba, 0x7b7, 0x7b3, 0x7b0, 0x7ac, 0x7a8, 0x7a5, 0x7a1, 0x79e, 0x79a, 0x797, 0x793, 0x790, 0x78c, 0x789, 0x785, 0x782, 0x77e, 0x77b, 0x778, 0x774, 0x771, 0x76d, 0x76a, 0x766, 0x763, 0x760, 0x75c, 0x759, 0x755, 0x752, 0x74f, 0x74b, 0x748, 0x744, 0x741, 0x73e, 0x73a, 0x737, 0x734, 0x730, 0x72d, 0x72a, 0x726, 0x723, 0x720, 0x71d, 0x719, 0x716, 0x713, 0x70f, 0x70c, 0x709, 0x706, 0x702, 0x6ff, 0x6fc, 0x6f9, 0x6f6, 0x6f2, 0x6ef, 0x6ec, 0x6e9, 0x6e6, 0x6e2, 0x6df, 0x6dc, 0x6d9, 0x6d6, 0x6d3, 0x6cf, 0x6cc, 0x6c9, 0x6c6, 0x6c3, 0x6c0, 0x6bd, 0x6ba, 0x6b6, 0x6b3, 0x6b0, 0x6ad, 0x6aa, 0x6a7, 0x6a4, 0x6a1, 0x69e, 0x69b, 0x698, 0x695, 0x692, 0x68f, 0x68c, 0x689, 0x685, 0x682, 0x67f, 0x67c, 0x679, 0x676, 0x674, 0x671, 0x66e, 0x66b, 0x668, 0x665, 0x662, 0x65f, 0x65c, 0x659, 0x656, 0x653, 0x650, 0x64d, 0x64a, 0x647, 0x644, 0x642, 0x63f, 0x63c, 0x639, 0x636, 0x633, 0x630, 0x62d, 0x62b, 0x628, 0x625, 0x622, 0x61f, 0x61c, 0x61a, 0x617, 0x614, 0x611, 0x60e, 0x60c, 0x609, 0x606, 0x603, 0x600, 0x5fe, 0x5fb, 0x5f8, 0x5f5, 0x5f3, 0x5f0, 0x5ed, 0x5ea, 0x5e8, 0x5e5, 0x5e2, 0x5e0, 0x5dd, 0x5da, 0x5d7, 0x5d5, 0x5d2, 0x5cf, 0x5cd, 0x5ca, 0x5c7, 0x5c5, 0x5c2, 0x5bf, 0x5bd, 0x5ba, 0x5b7, 0x5b5, 0x5b2, 0x5af, 0x5ad, 0x5aa, 0x5a8, 0x5a5, 0x5a2, 0x5a0, 0x59d, 0x59b, 0x598, 0x595, 0x593, 0x590, 0x58e, 0x58b, 0x589, 0x586, 0x583, 0x581, 0x57e, 0x57c, 0x579, 0x577, 0x574, 0x572, 0x56f, 0x56d, 0x56a, 0x568, 0x565, 0x563, 0x560, 0x55e, 0x55b, 0x559, 0x556, 0x554, 0x551, 0x54f, 0x54d, 0x54a, 0x548, 0x545, 0x543, 0x540, 0x53e, 0x53c, 0x539, 0x537, 0x534, 0x532, 0x52f, 0x52d, 0x52b, 0x528, 0x526, 0x524, 0x521, 0x51f, 0x51c, 0x51a, 0x518, 0x515, 0x513, 0x511, 0x50e, 0x50c, 0x50a, 0x507, 0x505, 0x503, 0x500, 0x4fe, 0x4fc, 0x4f9, 0x4f7, 0x4f5, 0x4f3, 0x4f0, 0x4ee, 0x4ec, 0x4e9, 0x4e7, 0x4e5, 0x4e3, 0x4e0, 0x4de, 0x4dc, 0x4da, 0x4d7, 0x4d5, 0x4d3, 0x4d1, 0x4cf, 0x4cc, 0x4ca, 0x4c8, 0x4c6, 0x4c3, 0x4c1, 0x4bf, 0x4bd, 0x4bb, 0x4b9, 0x4b6, 0x4b4, 0x4b2, 0x4b0, 0x4ae, 0x4ac, 0x4a9, 0x4a7, 0x4a5, 0x4a3, 0x4a1, 0x49f, 0x49d, 0x49a, 0x498, 0x496, 0x494, 0x492, 0x490, 0x48e, 0x48c, 0x489, 0x487, 0x485, 0x483, 0x481, 0x47f, 0x47d, 0x47b, 0x479, 0x477, 0x475, 0x473, 0x471, 0x46f, 0x46c, 0x46a, 0x468, 0x466, 0x464, 0x462, 0x460, 0x45e, 0x45c, 0x45a, 0x458, 0x456, 0x454, 0x452, 0x450, 0x44e, 0x44c, 0x44a, 0x448, 0x446, 0x444, 0x442, 0x440, 0x43e, 0x43c, 0x43b, 0x439, 0x437, 0x435, 0x433, 0x431, 0x42f, 0x42d, 0x42b, 0x429, 0x427, 0x425, 0x423, 0x421, 0x420, 0x41e, 0x41c, 0x41a, 0x418, 0x416, 0x414, 0x412, 0x410, 0x40f, 0x40d, 0x40b, 0x409, 0x407, 0x405, 0x403, 0x401, 0x400, 0x3fe, 0x3fc, 0x3fa, 0x3f8, 0x3f6, 0x3f5, 0x3f3, 0x3f1, 0x3ef, 0x3ed, 0x3eb, 0x3ea, 0x3e8, 0x3e6, 0x3e4, 0x3e2, 0x3e1, 0x3df, 0x3dd, 0x3db, 0x3da, 0x3d8, 0x3d6, 0x3d4, 0x3d2, 0x3d1, 0x3cf, 0x3cd, 0x3cb, 0x3ca, 0x3c8, 0x3c6, 0x3c4, 0x3c3, 0x3c1, 0x3bf, 0x3bd, 0x3bc, 0x3ba, 0x3b8, 0x3b7, 0x3b5, 0x3b3, 0x3b1, 0x3b0, 0x3ae, 0x3ac, 0x3ab, 0x3a9, 0x3a7, 0x3a6, 0x3a4, 0x3a2, 0x3a1, 0x39f, 0x39d, 0x39c, 0x39a, 0x398, 0x397, 0x395, 0x393, 0x392, 0x390, 0x38e, 0x38d, 0x38b, 0x389, 0x388, 0x386, 0x384, 0x383, 0x381, 0x380, 0x37e, 0x37c, 0x37b, 0x379, 0x378, 0x376, 0x374, 0x373, 0x371, 0x370, 0x36e, 0x36c, 0x36b, 0x369, 0x368, 0x366, 0x365, 0x363, 0x361, 0x360, 0x35e, 0x35d, 0x35b, 0x35a, 0x358, 0x357, 0x355, 0x353, 0x352, 0x350, 0x34f, 0x34d, 0x34c, 0x34a, 0x349, 0x347, 0x346, 0x344, 0x343, 0x341, 0x340, 0x33e, 0x33d, 0x33b, 0x33a, 0x338, 0x337, 0x335, 0x334, 0x332, 0x331, 0x32f, 0x32e, 0x32c, 0x32b, 0x32a, 0x328, 0x327, 0x325, 0x324, 0x322, 0x321, 0x31f, 0x31e, 0x31c, 0x31b, 0x31a, 0x318, 0x317, 0x315, 0x314, 0x312, 0x311, 0x310, 0x30e, 0x30d, 0x30b, 0x30a, 0x309, 0x307, 0x306, 0x304, 0x303, 0x302, 0x300, 0x2ff, 0x2fd, 0x2fc, 0x2fb, 0x2f9, 0x2f8, 0x2f7, 0x2f5, 0x2f4, 0x2f2, 0x2f1, 0x2f0, 0x2ee, 0x2ed, 0x2ec, 0x2ea, 0x2e9, 0x2e8, 0x2e6, 0x2e5, 0x2e4, 0x2e2, 0x2e1, 0x2e0, 0x2de, 0x2dd, 0x2dc, 0x2da, 0x2d9, 0x2d8, 0x2d6, 0x2d5, 0x2d4, 0x2d3, 0x2d1, 0x2d0, 0x2cf, 0x2cd, 0x2cc, 0x2cb, 0x2c9, 0x2c8, 0x2c7, 0x2c6, 0x2c4, 0x2c3, 0x2c2, 0x2c0, 0x2bf, 0x2be, 0x2bd, 0x2bb, 0x2ba, 0x2b9, 0x2b8, 0x2b6, 0x2b5, 0x2b4, 0x2b3, 0x2b1, 0x2b0, 0x2af, 0x2ae, 0x2ac, 0x2ab, 0x2aa, 0x2a9, 0x2a7, 0x2a6, 0x2a5, 0x2a4, 0x2a3, 0x2a1, 0x2a0, 0x29f, 0x29e, 0x29d, 0x29b, 0x29a, 0x299, 0x298, 0x297, 0x295, 0x294, 0x293, 0x292, 0x291, 0x28f, 0x28e, 0x28d, 0x28c, 0x28b, 0x28a, 0x288, 0x287, 0x286, 0x285, 0x284, 0x283, 0x281, 0x280, 0x27f, 0x27e, 0x27d, 0x27c, 0x27a, 0x279, 0x278, 0x277, 0x276, 0x275, 0x274, 0x272, 0x271, 0x270, 0x26f, 0x26e, 0x26d, 0x26c, 0x26b, 0x26a, 0x268, 0x267, 0x266, 0x265, 0x264, 0x263, 0x262, 0x261, 0x260, 0x25e, 0x25d, 0x25c, 0x25b, 0x25a, 0x259, 0x258, 0x257, 0x256, 0x255, 0x254, 0x253, 0x251, 0x250, 0x24f, 0x24e, 0x24d, 0x24c, 0x24b, 0x24a, 0x249, 0x248, 0x247, 0x246, 0x245, 0x244, 0x243, 0x242, 0x241, 0x240, 0x23e, 0x23d, 0x23c, 0x23b, 0x23a, 0x239, 0x238, 0x237, 0x236, 0x235, 0x234, 0x233, 0x232, 0x231, 0x230, 0x22f, 0x22e, 0x22d, 0x22c, 0x22b, 0x22a, 0x229, 0x228, 0x227, 0x226, 0x225, 0x224, 0x223, 0x222, 0x221, 0x220, 0x21f, 0x21e, 0x21d, 0x21c, 0x21b, 0x21a, 0x219, 0x218, 0x217, 0x216, 0x216, 0x215, 0x214, 0x213, 0x212, 0x211, 0x210, 0x20f, 0x20e, 0x20d, 0x20c, 0x20b, 0x20a, 0x209, 0x208, 0x207, 0x206, 0x205, 0x204, 0x204, 0x203, 0x202, 0x201, 0x200, 0x1ff, 0x1fe, 0x1fd, 0x1fc, 0x1fb, 0x1fa, 0x1f9, 0x1f8, 0x1f8, 0x1f7, 0x1f6, 0x1f5, 0x1f4, 0x1f3, 0x1f2, 0x1f1, 0x1f0, 0x1ef, 0x1ef, 0x1ee, 0x1ed, 0x1ec, 0x1eb, 0x1ea, 0x1e9, 0x1e8, 0x1e7, 0x1e7, 0x1e6, 0x1e5, 0x1e4, 0x1e3, 0x1e2, 0x1e1, 0x1e0, 0x1e0, 0x1df, 0x1de, 0x1dd, 0x1dc, 0x1db, 0x1da, 0x1da, 0x1d9, 0x1d8, 0x1d7, 0x1d6, 0x1d5, 0x1d4, 0x1d4, 0x1d3, 0x1d2, 0x1d1, 0x1d0, 0x1cf, 0x1cf, 0x1ce, 0x1cd, 0x1cc, 0x1cb, 0x1ca, 0x1ca, 0x1c9, 0x1c8, 0x1c7, 0x1c6, 0x1c6, 0x1c5, 0x1c4, 0x1c3, 0x1c2, 0x1c1, 0x1c1, 0x1c0, 0x1bf, 0x1be, 0x1bd, 0x1bd, 0x1bc, 0x1bb, 0x1ba, 0x1b9, 0x1b9, 0x1b8, 0x1b7, 0x1b6, 0x1b5, 0x1b5, 0x1b4, 0x1b3, 0x1b2, 0x1b1, 0x1b1, 0x1b0, 0x1af, 0x1ae, 0x1ae, 0x1ad, 0x1ac, 0x1ab, 0x1ab, 0x1aa, 0x1a9, 0x1a8, 0x1a7, 0x1a7, 0x1a6, 0x1a5, 0x1a4, 0x1a4, 0x1a3, 0x1a2, 0x1a1, 0x1a1, 0x1a0, 0x19f, 0x19e, 0x19e, 0x19d, 0x19c, 0x19b, 0x19b, 0x19a, 0x199, 0x198, 0x198, 0x197, 0x196, 0x195, 0x195, 0x194, 0x193, 0x193, 0x192, 0x191, 0x190, 0x190, 0x18f, 0x18e, 0x18e, 0x18d, 0x18c, 0x18b, 0x18b, 0x18a, 0x189, 0x189, 0x188, 0x187, 0x186, 0x186, 0x185, 0x184, 0x184, 0x183, 0x182, 0x182, 0x181, 0x180, 0x17f, 0x17f, 0x17e, 0x17d, 0x17d, 0x17c, 0x17b, 0x17b, 0x17a, 0x179, 0x179, 0x178, 0x177, 0x177, 0x176, 0x175, 0x175, 0x174, 0x173, 0x172, 0x172, 0x171, 0x170, 0x170, 0x16f, 0x16f, 0x16e, 0x16d, 0x16d, 0x16c, 0x16b, 0x16b, 0x16a, 0x169, 0x169, 0x168, 0x167, 0x167, 0x166, 0x165, 0x165, 0x164, 0x163, 0x163, 0x162, 0x162, 0x161, 0x160, 0x160, 0x15f, 0x15e, 0x15e, 0x15d, 0x15c, 0x15c, 0x15b, 0x15b, 0x15a, 0x159, 0x159, 0x158, 0x157, 0x157, 0x156, 0x156, 0x155, 0x154, 0x154, 0x153, 0x153, 0x152, 0x151, 0x151, 0x150, 0x14f, 0x14f, 0x14e, 0x14e, 0x14d, 0x14c, 0x14c, 0x14b, 0x14b, 0x14a, 0x149, 0x149, 0x148, 0x148, 0x147, 0x147, 0x146, 0x145, 0x145, 0x144, 0x144, 0x143, 0x142, 0x142, 0x141, 0x141, 0x140, 0x140, 0x13f, 0x13e, 0x13e, 0x13d, 0x13d, 0x13c, 0x13c, 0x13b, 0x13a, 0x13a, 0x139, 0x139, 0x138, 0x138, 0x137, 0x136, 0x136, 0x135, 0x135, 0x134, 0x134, 0x133, 0x133, 0x132, 0x131, 0x131, 0x130, 0x130, 0x12f, 0x12f, 0x12e, 0x12e, 0x12d, 0x12d, 0x12c, 0x12b, 0x12b, 0x12a, 0x12a, 0x129, 0x129, 0x128, 0x128, 0x127, 0x127, 0x126, 0x126, 0x125, 0x124, 0x124, 0x123, 0x123, 0x122, 0x122, 0x121, 0x121, 0x120, 0x120, 0x11f, 0x11f, 0x11e, 0x11e, 0x11d, 0x11d, 0x11c, 0x11c, 0x11b, 0x11b, 0x11a, 0x11a, 0x119, 0x119, 0x118, 0x118, 0x117, 0x117, 0x116, 0x116, 0x115, 0x115, 0x114, 0x114, 0x113, 0x113, 0x112, 0x112, 0x111, 0x111, 0x110, 0x110, 0x10f, 0x10f, 0x10e, 0x10e, 0x10d, 0x10d, 0x10c, 0x10c, 0x10b, 0x10b, 0x10a, 0x10a, 0x109, 0x109, 0x108, 0x108, 0x107, 0x107, 0x106, 0x106, 0x106, 0x105, 0x105, 0x104, 0x104, 0x103, 0x103, 0x102, 0x102, 0x101, 0x101, 0x100, 0x100, 0x0ff, 0x0ff, 0x0ff, 0x0fe, 0x0fe, 0x0fd, 0x0fd, 0x0fc, 0x0fc, 0x0fb, 0x0fb, 0x0fa, 0x0fa, 0x0fa, 0x0f9, 0x0f9, 0x0f8, 0x0f8, 0x0f7, 0x0f7, 0x0f6, 0x0f6, 0x0f5, 0x0f5, 0x0f5, 0x0f4, 0x0f4, 0x0f3, 0x0f3, 0x0f2, 0x0f2, 0x0f2, 0x0f1, 0x0f1, 0x0f0, 0x0f0, 0x0ef, 0x0ef, 0x0ef, 0x0ee, 0x0ee, 0x0ed, 0x0ed, 0x0ec, 0x0ec, 0x0ec, 0x0eb, 0x0eb, 0x0ea, 0x0ea, 0x0e9, 0x0e9, 0x0e9, 0x0e8, 0x0e8, 0x0e7, 0x0e7, 0x0e6, 0x0e6, 0x0e6, 0x0e5, 0x0e5, 0x0e4, 0x0e4, 0x0e4, 0x0e3, 0x0e3, 0x0e2, 0x0e2, 0x0e2, 0x0e1, 0x0e1, 0x0e0, 0x0e0, 0x0e0, 0x0df, 0x0df, 0x0de, 0x0de, 0x0dd, 0x0dd, 0x0dd, 0x0dc, 0x0dc, 0x0dc, 0x0db, 0x0db, 0x0da, 0x0da, 0x0da, 0x0d9, 0x0d9, 0x0d8, 0x0d8, 0x0d8, 0x0d7, 0x0d7, 0x0d6, 0x0d6, 0x0d6, 0x0d5, 0x0d5, 0x0d4, 0x0d4, 0x0d4, 0x0d3, 0x0d3, 0x0d3, 0x0d2, 0x0d2, 0x0d1, 0x0d1, 0x0d1, 0x0d0, 0x0d0, 0x0d0, 0x0cf, 0x0cf, 0x0ce, 0x0ce, 0x0ce, 0x0cd, 0x0cd, 0x0cd, 0x0cc, 0x0cc, 0x0cb, 0x0cb, 0x0cb, 0x0ca, 0x0ca, 0x0ca, 0x0c9, 0x0c9, 0x0c9, 0x0c8, 0x0c8, 0x0c7, 0x0c7, 0x0c7, 0x0c6, 0x0c6, 0x0c6, 0x0c5, 0x0c5, 0x0c5, 0x0c4, 0x0c4, 0x0c4, 0x0c3, 0x0c3, 0x0c3, 0x0c2, 0x0c2, 0x0c1, 0x0c1, 0x0c1, 0x0c0, 0x0c0, 0x0c0, 0x0bf, 0x0bf, 0x0bf, 0x0be, 0x0be, 0x0be, 0x0bd, 0x0bd, 0x0bd, 0x0bc, 0x0bc, 0x0bc, 0x0bb, 0x0bb, 0x0bb, 0x0ba, 0x0ba, 0x0ba, 0x0b9, 0x0b9, 0x0b9, 0x0b8, 0x0b8, 0x0b8, 0x0b7, 0x0b7, 0x0b7, 0x0b6, 0x0b6, 0x0b6, 0x0b5, 0x0b5, 0x0b5, 0x0b4, 0x0b4, 0x0b4, 0x0b3, 0x0b3, 0x0b3, 0x0b2, 0x0b2, 0x0b2, 0x0b1, 0x0b1, 0x0b1, 0x0b0, 0x0b0, 0x0b0, 0x0af, 0x0af, 0x0af, 0x0af, 0x0ae, 0x0ae, 0x0ae, 0x0ad, 0x0ad, 0x0ad, 0x0ac, 0x0ac, 0x0ac, 0x0ab, 0x0ab, 0x0ab, 0x0aa, 0x0aa, 0x0aa, 0x0aa, 0x0a9, 0x0a9, 0x0a9, 0x0a8, 0x0a8, 0x0a8, 0x0a7, 0x0a7, 0x0a7, 0x0a7, 0x0a6, 0x0a6, 0x0a6, 0x0a5, 0x0a5, 0x0a5, 0x0a4, 0x0a4, 0x0a4, 0x0a4, 0x0a3, 0x0a3, 0x0a3, 0x0a2, 0x0a2, 0x0a2, 0x0a2, 0x0a1, 0x0a1, 0x0a1, 0x0a0, 0x0a0, 0x0a0, 0x09f, 0x09f, 0x09f, 0x09f, 0x09e, 0x09e, 0x09e, 0x09d, 0x09d, 0x09d, 0x09d, 0x09c, 0x09c, 0x09c, 0x09b, 0x09b, 0x09b, 0x09b, 0x09a, 0x09a, 0x09a, 0x09a, 0x099, 0x099, 0x099, 0x098, 0x098, 0x098, 0x098, 0x097, 0x097, 0x097, 0x097, 0x096, 0x096, 0x096, 0x095, 0x095, 0x095, 0x095, 0x094, 0x094, 0x094, 0x094, 0x093, 0x093, 0x093, 0x093, 0x092, 0x092, 0x092, 0x091, 0x091, 0x091, 0x091, 0x090, 0x090, 0x090, 0x090, 0x08f, 0x08f, 0x08f, 0x08f, 0x08e, 0x08e, 0x08e, 0x08e, 0x08d, 0x08d, 0x08d, 0x08d, 0x08c, 0x08c, 0x08c, 0x08c, 0x08b, 0x08b, 0x08b, 0x08b, 0x08a, 0x08a, 0x08a, 0x08a, 0x089, 0x089, 0x089, 0x089, 0x088, 0x088, 0x088, 0x088, 0x087, 0x087, 0x087, 0x087, 0x086, 0x086, 0x086, 0x086, 0x085, 0x085, 0x085, 0x085, 0x084, 0x084, 0x084, 0x084, 0x083, 0x083, 0x083, 0x083, 0x083, 0x082, 0x082, 0x082, 0x082, 0x081, 0x081, 0x081, 0x081, 0x080, 0x080, 0x080, 0x080, 0x07f, 0x07f, 0x07f, 0x07f, 0x07f, 0x07e, 0x07e, 0x07e, 0x07e, 0x07d, 0x07d, 0x07d, 0x07d, 0x07d, 0x07c, 0x07c, 0x07c, 0x07c, 0x07b, 0x07b, 0x07b, 0x07b, 0x07b, 0x07a, 0x07a, 0x07a, 0x07a, 0x079, 0x079, 0x079, 0x079, 0x079, 0x078, 0x078, 0x078, 0x078, 0x077, 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f }; const uint16_t PitchConverter::centTableSSGTriangle_[3072] = { 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001 }; const uint16_t PitchConverter::centTableSSGSaw_[3072] = { 0x0ef, 0x0ee, 0x0ee, 0x0ed, 0x0ed, 0x0ec, 0x0ec, 0x0ec, 0x0eb, 0x0eb, 0x0ea, 0x0ea, 0x0e9, 0x0e9, 0x0e9, 0x0e8, 0x0e8, 0x0e7, 0x0e7, 0x0e6, 0x0e6, 0x0e6, 0x0e5, 0x0e5, 0x0e4, 0x0e4, 0x0e4, 0x0e3, 0x0e3, 0x0e2, 0x0e2, 0x0e2, 0x0e1, 0x0e1, 0x0e0, 0x0e0, 0x0e0, 0x0df, 0x0df, 0x0de, 0x0de, 0x0dd, 0x0dd, 0x0dd, 0x0dc, 0x0dc, 0x0dc, 0x0db, 0x0db, 0x0da, 0x0da, 0x0da, 0x0d9, 0x0d9, 0x0d8, 0x0d8, 0x0d8, 0x0d7, 0x0d7, 0x0d6, 0x0d6, 0x0d6, 0x0d5, 0x0d5, 0x0d4, 0x0d4, 0x0d4, 0x0d3, 0x0d3, 0x0d3, 0x0d2, 0x0d2, 0x0d1, 0x0d1, 0x0d1, 0x0d0, 0x0d0, 0x0d0, 0x0cf, 0x0cf, 0x0ce, 0x0ce, 0x0ce, 0x0cd, 0x0cd, 0x0cd, 0x0cc, 0x0cc, 0x0cb, 0x0cb, 0x0cb, 0x0ca, 0x0ca, 0x0ca, 0x0c9, 0x0c9, 0x0c9, 0x0c8, 0x0c8, 0x0c7, 0x0c7, 0x0c7, 0x0c6, 0x0c6, 0x0c6, 0x0c5, 0x0c5, 0x0c5, 0x0c4, 0x0c4, 0x0c4, 0x0c3, 0x0c3, 0x0c3, 0x0c2, 0x0c2, 0x0c1, 0x0c1, 0x0c1, 0x0c0, 0x0c0, 0x0c0, 0x0bf, 0x0bf, 0x0bf, 0x0be, 0x0be, 0x0be, 0x0bd, 0x0bd, 0x0bd, 0x0bc, 0x0bc, 0x0bc, 0x0bb, 0x0bb, 0x0bb, 0x0ba, 0x0ba, 0x0ba, 0x0b9, 0x0b9, 0x0b9, 0x0b8, 0x0b8, 0x0b8, 0x0b7, 0x0b7, 0x0b7, 0x0b6, 0x0b6, 0x0b6, 0x0b5, 0x0b5, 0x0b5, 0x0b4, 0x0b4, 0x0b4, 0x0b3, 0x0b3, 0x0b3, 0x0b2, 0x0b2, 0x0b2, 0x0b1, 0x0b1, 0x0b1, 0x0b0, 0x0b0, 0x0b0, 0x0af, 0x0af, 0x0af, 0x0af, 0x0ae, 0x0ae, 0x0ae, 0x0ad, 0x0ad, 0x0ad, 0x0ac, 0x0ac, 0x0ac, 0x0ab, 0x0ab, 0x0ab, 0x0aa, 0x0aa, 0x0aa, 0x0aa, 0x0a9, 0x0a9, 0x0a9, 0x0a8, 0x0a8, 0x0a8, 0x0a7, 0x0a7, 0x0a7, 0x0a7, 0x0a6, 0x0a6, 0x0a6, 0x0a5, 0x0a5, 0x0a5, 0x0a4, 0x0a4, 0x0a4, 0x0a4, 0x0a3, 0x0a3, 0x0a3, 0x0a2, 0x0a2, 0x0a2, 0x0a2, 0x0a1, 0x0a1, 0x0a1, 0x0a0, 0x0a0, 0x0a0, 0x09f, 0x09f, 0x09f, 0x09f, 0x09e, 0x09e, 0x09e, 0x09d, 0x09d, 0x09d, 0x09d, 0x09c, 0x09c, 0x09c, 0x09b, 0x09b, 0x09b, 0x09b, 0x09a, 0x09a, 0x09a, 0x09a, 0x099, 0x099, 0x099, 0x098, 0x098, 0x098, 0x098, 0x097, 0x097, 0x097, 0x097, 0x096, 0x096, 0x096, 0x095, 0x095, 0x095, 0x095, 0x094, 0x094, 0x094, 0x094, 0x093, 0x093, 0x093, 0x093, 0x092, 0x092, 0x092, 0x091, 0x091, 0x091, 0x091, 0x090, 0x090, 0x090, 0x090, 0x08f, 0x08f, 0x08f, 0x08f, 0x08e, 0x08e, 0x08e, 0x08e, 0x08d, 0x08d, 0x08d, 0x08d, 0x08c, 0x08c, 0x08c, 0x08c, 0x08b, 0x08b, 0x08b, 0x08b, 0x08a, 0x08a, 0x08a, 0x08a, 0x089, 0x089, 0x089, 0x089, 0x088, 0x088, 0x088, 0x088, 0x087, 0x087, 0x087, 0x087, 0x086, 0x086, 0x086, 0x086, 0x085, 0x085, 0x085, 0x085, 0x084, 0x084, 0x084, 0x084, 0x083, 0x083, 0x083, 0x083, 0x083, 0x082, 0x082, 0x082, 0x082, 0x081, 0x081, 0x081, 0x081, 0x080, 0x080, 0x080, 0x080, 0x07f, 0x07f, 0x07f, 0x07f, 0x07f, 0x07e, 0x07e, 0x07e, 0x07e, 0x07d, 0x07d, 0x07d, 0x07d, 0x07d, 0x07c, 0x07c, 0x07c, 0x07c, 0x07b, 0x07b, 0x07b, 0x07b, 0x07b, 0x07a, 0x07a, 0x07a, 0x07a, 0x079, 0x079, 0x079, 0x079, 0x079, 0x078, 0x078, 0x078, 0x078, 0x077, 0x077, 0x077, 0x077, 0x077, 0x076, 0x076, 0x076, 0x076, 0x076, 0x075, 0x075, 0x075, 0x075, 0x074, 0x074, 0x074, 0x074, 0x074, 0x073, 0x073, 0x073, 0x073, 0x073, 0x072, 0x072, 0x072, 0x072, 0x072, 0x071, 0x071, 0x071, 0x071, 0x071, 0x070, 0x070, 0x070, 0x070, 0x070, 0x06f, 0x06f, 0x06f, 0x06f, 0x06f, 0x06e, 0x06e, 0x06e, 0x06e, 0x06e, 0x06d, 0x06d, 0x06d, 0x06d, 0x06d, 0x06c, 0x06c, 0x06c, 0x06c, 0x06c, 0x06b, 0x06b, 0x06b, 0x06b, 0x06b, 0x06a, 0x06a, 0x06a, 0x06a, 0x06a, 0x069, 0x069, 0x069, 0x069, 0x069, 0x069, 0x068, 0x068, 0x068, 0x068, 0x068, 0x067, 0x067, 0x067, 0x067, 0x067, 0x066, 0x066, 0x066, 0x066, 0x066, 0x066, 0x065, 0x065, 0x065, 0x065, 0x065, 0x064, 0x064, 0x064, 0x064, 0x064, 0x064, 0x063, 0x063, 0x063, 0x063, 0x063, 0x062, 0x062, 0x062, 0x062, 0x062, 0x062, 0x061, 0x061, 0x061, 0x061, 0x061, 0x061, 0x060, 0x060, 0x060, 0x060, 0x060, 0x060, 0x05f, 0x05f, 0x05f, 0x05f, 0x05f, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05e, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05d, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05c, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05b, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x05a, 0x059, 0x059, 0x059, 0x059, 0x059, 0x059, 0x058, 0x058, 0x058, 0x058, 0x058, 0x058, 0x057, 0x057, 0x057, 0x057, 0x057, 0x057, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x056, 0x055, 0x055, 0x055, 0x055, 0x055, 0x055, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x054, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x053, 0x052, 0x052, 0x052, 0x052, 0x052, 0x052, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x051, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x050, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04f, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04e, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04d, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04c, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04b, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x04a, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x049, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x048, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x047, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x046, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x045, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x044, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x043, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x042, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x041, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x040, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03f, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03e, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03d, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03c, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03b, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x03a, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x039, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x038, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x037, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x036, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x035, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x034, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x033, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x032, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x031, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x030, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02f, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02e, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02d, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02c, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02b, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x02a, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x029, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x028, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x027, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x026, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x025, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x024, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x023, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x022, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x021, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x020, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01f, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01e, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01d, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01c, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01b, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x01a, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x019, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x018, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x017, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x016, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x015, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x014, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x013, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x012, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x011, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x010, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00f, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00e, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00d, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00c, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00b, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x00a, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x009, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x008, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x007, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x006, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001 }; PitchConverter::PitchConverter() { } BambooTracker-0.3.5/BambooTracker/pitch_converter.hpp000066400000000000000000000015001362177441300226600ustar00rootroot00000000000000#pragma once #include #include "misc.hpp" class PitchConverter { public: static uint16_t getPitchFM(Note note, int octave, int pitch); static uint16_t getPitchSSGSquare(Note note, int octave, int pitch); static uint16_t getPitchSSGSquare(int n); static uint16_t getPitchSSGTriangle(Note note, int octave, int pitch); static uint16_t getPitchSSGSaw(Note note, int octave, int pitch); private: static const uint16_t centTableFM_[3072]; static const uint16_t centTableSSGSquare_[3072]; static const uint16_t centTableSSGTriangle_[3072]; static const uint16_t centTableSSGSaw_[3072]; PitchConverter(); static int calculateIndex(int octave, Note note, int pitch) { int idx = 384 * octave + static_cast(note) + pitch; if (idx < 0) return 0; else if (idx > 3071) return 3071; else return idx; } }; BambooTracker-0.3.5/BambooTracker/playback.cpp000066400000000000000000001436611362177441300212620ustar00rootroot00000000000000#include "playback.hpp" #include #include "song.hpp" #include "misc.hpp" PlaybackManager::PlaybackManager(std::shared_ptr opnaCtrl, std::weak_ptr instMan, std::weak_ptr tickCounter, std::weak_ptr mod, bool isRetrieveChannel) : opnaCtrl_(opnaCtrl), instMan_(instMan), tickCounter_(tickCounter), mod_(mod), curSongNum_(0), playOrderNum_(-1), playStepNum_(-1), playState_(0), managerState_(PlaybackState::Stop), isFindNextStep_(false), isRetrieveChannel_(isRetrieveChannel) { songStyle_ = mod.lock()->getSong(curSongNum_).getStyle(); clearEffectMaps(); clearNoteDelayCounts(); clearDelayBeyondStepCounts(); } void PlaybackManager::setSong(std::weak_ptr mod, int songNum) { mod_ = mod; curSongNum_ = songNum; songStyle_ = mod_.lock()->getSong(curSongNum_).getStyle(); /* opna mode is changed in BambooTracker class */ size_t fmch = getFMChannelCount(songStyle_.type); isNoteDelayFM_ = std::vector(fmch); keyOnBasedEffsFM_ = std::vector>(fmch); stepBeginBasedEffsFM_ = std::vector>(fmch); ntDlyCntFM_ = std::vector(fmch); ntCutDlyCntFM_ = std::vector(fmch); volDlyCntFM_ = std::vector(fmch); volDlyValueFM_ = std::vector(fmch, -1); tposeDlyCntFM_ = std::vector(fmch); tposeDlyValueFM_ = std::vector(fmch); isNoteDelaySSG_ = std::vector(3); keyOnBasedEffsSSG_ = std::vector>(3); stepBeginBasedEffsSSG_ = std::vector>(3); ntDlyCntSSG_ = std::vector(3); ntCutDlyCntSSG_ = std::vector(3); volDlyCntSSG_ = std::vector(3); volDlyValueSSG_ = std::vector(3, -1); tposeDlyCntSSG_ = std::vector(3); tposeDlyValueSSG_ = std::vector(3); isNoteDelayDrum_ = std::vector(6); keyOnBasedEffsDrum_ = std::vector>(6); stepBeginBasedEffsDrum_ = std::vector>(6); ntDlyCntDrum_ = std::vector(6); ntCutDlyCntDrum_ = std::vector(6); volDlyCntDrum_ = std::vector(6); volDlyValueDrum_ = std::vector(6, -1); } /********** Play song **********/ void PlaybackManager::startPlaySong(int order) { std::lock_guard lock(mutex_); startPlay(); playState_ = 0x01; managerState_ = PlaybackState::PlaySong; playStepNum_ = 0; playOrderNum_ = order; findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlayFromStart() { std::lock_guard lock(mutex_); startPlay(); playState_ = 0x01; managerState_ = PlaybackState::PlayFromStart; playOrderNum_ = 0; playStepNum_ = 0; findNextStep(); } void PlaybackManager::startPlayPattern(int order) { std::lock_guard lock(mutex_); startPlay(); playState_ = 0x11; managerState_ = PlaybackState::PlayPattern; playStepNum_ = 0; playOrderNum_ = order; findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlayFromCurrentStep(int order, int step) { std::lock_guard lock(mutex_); startPlay(); playState_ = 0x01; managerState_ = PlaybackState::PlayFromCurrentStep; playOrderNum_ = order; playStepNum_ = step; findNextStep(); if (isRetrieveChannel_) retrieveChannelStates(); } void PlaybackManager::startPlay() { opnaCtrl_->reset(); Song& song = mod_.lock()->getSong(curSongNum_); tickCounter_.lock()->setTempo(song.getTempo()); tickCounter_.lock()->setSpeed(song.getSpeed()); tickCounter_.lock()->setGroove(mod_.lock()->getGroove(song.getGroove()).getSequence()); tickCounter_.lock()->setGrooveTrigger(song.isUsedTempo() ? GrooveTrigger::Invalid : GrooveTrigger::ValidByGlobal); tickCounter_.lock()->resetCount(); tickCounter_.lock()->setPlayState(true); clearEffectMaps(); clearNoteDelayCounts(); clearDelayBeyondStepCounts(); } void PlaybackManager::stopPlaySong() { std::lock_guard lock(mutex_); stopPlay(); } void PlaybackManager::stopPlay() { // No mutex to call from PlaybackManager::streamCountUp opnaCtrl_->reset(); tickCounter_.lock()->setPlayState(false); playState_ = 0; managerState_ = PlaybackState::Stop; playOrderNum_ = -1; playStepNum_ = -1; } bool PlaybackManager::isPlaySong() const { return ((playState_ & 0x01) > 0); } PlaybackState PlaybackManager::getPlaybackState() const { return managerState_; } int PlaybackManager::getPlayingOrderNumber() const { return playOrderNum_; } int PlaybackManager::getPlayingStepNumber() const { return playStepNum_; } /********** Stream events **********/ int PlaybackManager::streamCountUp() { std::lock_guard lock(mutex_); int state = tickCounter_.lock()->countUp(); if (state > 0) { checkValidPosition(); tickProcess(state); } else if (!state) { checkValidPosition(); if (stepDown()) { stepProcess(); if (!isFindNextStep_) findNextStep(); } else { stopPlay(); } } else { for (auto& attrib : songStyle_.trackAttribs) { opnaCtrl_->tickEvent(attrib.source, attrib.channelInSource); } } return state; } bool PlaybackManager::stepDown() { if (playState_ & 0x02) { // Foward current step if (nextReadOrder_ == -1) { isFindNextStep_ = false; return false; } else { playOrderNum_ = nextReadOrder_; playStepNum_ = nextReadStep_; } } else { // First read playState_ |= 0x02; } return true; } void PlaybackManager::findNextStep() { // Init nextReadOrder_ = playOrderNum_; nextReadStep_ = playStepNum_; // Search int ptnSize = static_cast(getPatternSizeFromOrderNumber(curSongNum_, nextReadOrder_)); if (!ptnSize || nextReadStep_ >= ptnSize - 1) { if (!(playState_ & 0x10)) { // Not play pattern if (nextReadOrder_ >= static_cast(getOrderSize(curSongNum_)) - 1) { nextReadOrder_ = 0; } else { ++nextReadOrder_; } } nextReadStep_ = 0; } else { ++nextReadStep_; } isFindNextStep_ = true; } void PlaybackManager::checkValidPosition() { auto& song = mod_.lock()->getSong(curSongNum_); int orderSize = static_cast(song.getOrderSize()); if (playOrderNum_ >= orderSize) { playOrderNum_ = 0; playStepNum_ = 0; nextReadOrder_ = 0; nextReadStep_ = 0; } else if (playStepNum_ >= static_cast(song.getPatternSizeFromOrderNumber(playOrderNum_))) { if (playOrderNum_ == orderSize - 1) { playOrderNum_ = 0; playStepNum_ = 0; nextReadOrder_ = 0; nextReadStep_ = 0; } else { ++playOrderNum_; playStepNum_ = 0; nextReadOrder_ = playOrderNum_; nextReadStep_ = 0; } } } /// Register update order: volume -> instrument -> effect -> key on void PlaybackManager::stepProcess() { clearNoteDelayCounts(); updateDelayEventCounts(); auto& song = mod_.lock()->getSong(curSongNum_); // Store effects from the step to map for (auto& attrib : songStyle_.trackAttribs) { auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(playOrderNum_).getStep(playStepNum_); size_t uch = static_cast(attrib.channelInSource); switch (attrib.source) { case SoundSource::FM: { keyOnBasedEffsFM_.at(uch).clear(); bool isDelay = false; for (int i = 0; i < 4; ++i) { Effect&& eff = Effect::makeEffectData(SoundSource::FM, step.getEffectID(i), step.getEffectValue(i)); isDelay |= storeEffectToMapFM(attrib.channelInSource, std::move(eff)); } isNoteDelayFM_.at(uch) = isDelay; break; } case SoundSource::SSG: { keyOnBasedEffsSSG_.at(uch).clear(); bool isDelay = false; for (int i = 0; i < 4; ++i) { Effect&& eff = Effect::makeEffectData(SoundSource::SSG, step.getEffectID(i), step.getEffectValue(i)); isDelay |= storeEffectToMapSSG(attrib.channelInSource, std::move(eff)); } isNoteDelaySSG_.at(uch) = isDelay; break; } case SoundSource::DRUM: { keyOnBasedEffsDrum_.at(uch).clear(); bool isDelay = false; for (int i = 0; i < 4; ++i) { Effect&& eff = Effect::makeEffectData(SoundSource::DRUM, step.getEffectID(i), step.getEffectValue(i)); isDelay |= storeEffectToMapDrum(attrib.channelInSource, std::move(eff)); } isNoteDelayDrum_.at(uch) = isDelay; break; } } } // Execute step events bool isNextSet = executeStoredEffectsGlobal(); for (auto& attrib : songStyle_.trackAttribs) { auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(playOrderNum_).getStep(playStepNum_); switch (attrib.source) { case SoundSource::FM: if (isNoteDelayFM_.at(attrib.channelInSource)) { // Set effect executeStoredEffectsFM(attrib.channelInSource); checkFMNoteDelayAndEnvelopeReset(step, attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::FM, attrib.channelInSource); } else { executeFMStepEvents(step, attrib.channelInSource); } break; case SoundSource::SSG: if (isNoteDelaySSG_.at(attrib.channelInSource)) { // Set effect executeStoredEffectsSSG(attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::SSG, attrib.channelInSource); } else { executeSSGStepEvents(step, attrib.channelInSource); } break; case SoundSource::DRUM: if (isNoteDelayDrum_.at(attrib.channelInSource)) { // Set effect executeStoredEffectsDrum(attrib.channelInSource); opnaCtrl_->tickEvent(SoundSource::DRUM, attrib.channelInSource); } else { executeDrumStepEvents(step, attrib.channelInSource); } break; } } opnaCtrl_->updateRegisterStates(); // Update for other changes isFindNextStep_ = isNextSet; } void PlaybackManager::executeFMStepEvents(Step& step, int ch, bool calledByNoteDelay) { int noteNum = step.getNoteNumber(); if (!calledByNoteDelay && noteNum != -1) clearFMDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (0 <= vol && vol < 0x80) { opnaCtrl_->setVolumeFM(ch, vol); } // Set instrument if (step.getInstrumentNumber() != -1) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) opnaCtrl_->setInstrumentFM(ch, inst); } else { opnaCtrl_->restoreFMEnvelopeFromReset(ch); } // Set effect executeStoredEffectsFM(ch); // Set key switch (noteNum) { case -1: // None if (!calledByNoteDelay) { // When this is called by note delay, tick event will be updated in readTick checkFMDelayEventsInTick(step, ch); opnaCtrl_->tickEvent(SoundSource::FM, ch); } break; case -2: // Key off opnaCtrl_->keyOffFM(ch); break; case -3: // Echo 0 opnaCtrl_->keyOnFM(ch, 0); break; case -4: // Echo 1 opnaCtrl_->keyOnFM(ch, 1); break; case -5: // Echo 2 opnaCtrl_->keyOnFM(ch, 2); break; case -6: // Echo 3 opnaCtrl_->keyOnFM(ch, 3); break; default: // Key on { std::pair octNote = noteNumberToOctaveAndNote(step.getNoteNumber()); opnaCtrl_->keyOnFM(ch, octNote.second, octNote.first, 0); break; } } } void PlaybackManager::executeSSGStepEvents(Step& step, int ch, bool calledByNoteDelay) { int noteNum = step.getNoteNumber(); if (!calledByNoteDelay && noteNum != -1) clearSSGDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (0 <= vol && vol < 0x10) { opnaCtrl_->setVolumeSSG(ch, vol); } // Set instrument if (step.getInstrumentNumber() != -1) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) opnaCtrl_->setInstrumentSSG(ch, inst); } // Set effect executeStoredEffectsSSG(ch); // Set key switch (noteNum) { case -1: // None if (!calledByNoteDelay) { // When this is called by note delay, tick event will be updated in readTick checkSSGDelayEventsInTick(step, ch); opnaCtrl_->tickEvent(SoundSource::SSG, ch); } break; case -2: // Key off opnaCtrl_->keyOffSSG(ch); break; case -3: // Echo 0 opnaCtrl_->keyOnSSG(ch, 0); break; case -4: // Echo 1 opnaCtrl_->keyOnSSG(ch, 1); break; case -5: // Echo 2 opnaCtrl_->keyOnSSG(ch, 2); break; case -6: // Echo 3 opnaCtrl_->keyOnSSG(ch, 3); break; default: // Key on { std::pair octNote = noteNumberToOctaveAndNote(step.getNoteNumber()); opnaCtrl_->keyOnSSG(ch, octNote.second, octNote.first, 0); break; } } } void PlaybackManager::executeDrumStepEvents(Step& step, int ch, bool calledByNoteDelay) { int noteNum = step.getNoteNumber(); if (!calledByNoteDelay && noteNum != -1) clearDrumDelayBeyondStepCounts(ch); // Except no key event // Set volume int vol = step.getVolume(); if (0 <= vol && vol < 0x20) { opnaCtrl_->setVolumeDrum(ch, vol); } // Set effect executeStoredEffectsDrum(ch); // Set key switch (noteNum) { case -1: // None if (!calledByNoteDelay) { // When this is called by note delay, tick event will be updated in readTick checkDrumDelayEventsInTick(step, ch); opnaCtrl_->tickEvent(SoundSource::DRUM, ch); } break; case -2: // Key off opnaCtrl_->setKeyOffFlagDrum(ch); break; default: // Key on & Echo opnaCtrl_->setKeyOnFlagDrum(ch); break; } } bool PlaybackManager::executeStoredEffectsGlobal() { bool changedNextPos = false; // Read step end based effects for (const auto& eff : stepEndBasedEffsGlobal_) { switch (eff.first) { case EffectType::PositionJump: changedNextPos |= effPositionJump(eff.second); break; case EffectType::SongEnd: effSongEnd(); changedNextPos = true; break; case EffectType::PatternBreak: changedNextPos |= effPatternBreak(eff.second); break; default: break; } } stepEndBasedEffsGlobal_.clear(); // Read step beginning based effects auto&& it = stepBeginBasedEffsGlobal_.find(EffectType::SpeedTempoChange); if (it != stepBeginBasedEffsGlobal_.end()) { if (it->second < 0x20) effSpeedChange(it->second); else effTempoChange(it->second); } for (const auto& eff : stepBeginBasedEffsGlobal_) { switch (eff.first) { case EffectType::Groove: if (eff.second < static_cast(mod_.lock()->getGrooveCount())) effGrooveChange(eff.second); break; default: break; } } stepBeginBasedEffsGlobal_.clear(); return changedNextPos; } bool PlaybackManager::storeEffectToMapFM(int ch, Effect eff) { switch (eff.type) { case EffectType::Arpeggio: case EffectType::PortamentoUp: case EffectType::PortamentoDown: case EffectType::TonePortamento: case EffectType::Vibrato: case EffectType::Tremolo: case EffectType::Pan: case EffectType::VolumeSlide: case EffectType::Detune: case EffectType::NoteSlideUp: case EffectType::NoteSlideDown: case EffectType::NoteCut: case EffectType::TransposeDelay: case EffectType::VolumeDelay: case EffectType::FBControl: case EffectType::TLControl: case EffectType::MLControl: case EffectType::ARControl: case EffectType::DRControl: case EffectType::RRControl: case EffectType::RegisterAddress0: case EffectType::RegisterAddress1: case EffectType::RegisterValue: case EffectType::Brightness: keyOnBasedEffsFM_.at(static_cast(ch))[eff.type] = eff.value; return false; case EffectType::SpeedTempoChange: case EffectType::Groove: stepBeginBasedEffsGlobal_[eff.type] = eff.value; return false; case EffectType::NoteDelay: if (eff.value < tickCounter_.lock()->getSpeed()) { stepBeginBasedEffsFM_.at(static_cast(ch))[EffectType::NoteDelay] = eff.value; return true; } return false; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: stepEndBasedEffsGlobal_[eff.type] = eff.value; return false; default: return false; } } void PlaybackManager::executeStoredEffectsFM(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects for (const auto& eff : stepBeginBasedEffsFM_.at(uch)) { switch (eff.first) { case EffectType::NoteDelay: ntDlyCntFM_.at(uch) = eff.second; isNoteDelay = true; break; default: break; } } stepBeginBasedEffsFM_.at(uch).clear(); // Read note on and step beginning based effects if (!isNoteDelay) { for (const auto& eff : keyOnBasedEffsFM_.at(uch)) { switch (eff.first) { case EffectType::Arpeggio: opnaCtrl_->setArpeggioEffectFM(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::PortamentoUp: opnaCtrl_->setPortamentoEffectFM(ch, eff.second); break; case EffectType::PortamentoDown: opnaCtrl_->setPortamentoEffectFM(ch, -eff.second); break; case EffectType::TonePortamento: opnaCtrl_->setPortamentoEffectFM(ch, eff.second, true); break; case EffectType::Vibrato: opnaCtrl_->setVibratoEffectFM(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::Tremolo: opnaCtrl_->setTremoloEffectFM(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::Pan: if (-1 < eff.second && eff.second < 4) opnaCtrl_->setPanFM(ch, eff.second); break; case EffectType::VolumeSlide: { int hi = eff.second >> 4; int low = eff.second & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideFM(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideFM(ch, low, false); // Slide down break; } case EffectType::Detune: opnaCtrl_->setDetuneFM(ch, eff.second - 0x80); break; case EffectType::NoteSlideUp: opnaCtrl_->setNoteSlideFM(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::NoteSlideDown: opnaCtrl_->setNoteSlideFM(ch, eff.second >> 4, -(eff.second & 0x0f)); break; case EffectType::NoteCut: ntCutDlyCntFM_.at(uch) = eff.second; break; case EffectType::TransposeDelay: tposeDlyCntFM_.at(uch) = (eff.second & 0x70) >> 4; tposeDlyValueFM_.at(uch) = ((eff.second & 0x80) ? -1 : 1) * (eff.second & 0x0f); break; case EffectType::VolumeDelay: { int count = eff.second >> 8; if (count > 0) { volDlyCntFM_.at(uch) = count; volDlyValueFM_.at(uch) = eff.second & 0x00ff; } break; } case EffectType::FBControl: if (-1 < eff.second && eff.second < 8) opnaCtrl_->setFBControlFM(ch, eff.second); break; case EffectType::TLControl: { int op = eff.second >> 8; int val = eff.second & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 128) opnaCtrl_->setTLControlFM(ch, op - 1, val); break; } case EffectType::MLControl: { int op = eff.second >> 4; int val = eff.second & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setMLControlFM(ch, op - 1, val); break; } case EffectType::ARControl: { int op = eff.second >> 8; int val = eff.second & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setARControlFM(ch, op - 1, val); break; } case EffectType::DRControl: { int op = eff.second >> 8; int val = eff.second & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setDRControlFM(ch, op - 1, val); break; } case EffectType::RRControl: { int op = eff.second >> 4; int val = eff.second & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setRRControlFM(ch, op - 1, val); break; } case EffectType::RegisterAddress0: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(0, eff.second); break; } case EffectType::RegisterAddress1: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(1, eff.second); break; } case EffectType::RegisterValue: { if (-1 < eff.second) opnaCtrl_->sendRegisterValue(eff.second); break; } case EffectType::Brightness: { if (0 < eff.second) opnaCtrl_->setBrightnessFM(ch, eff.second - 0x80); break; } default: break; } } keyOnBasedEffsFM_.at(uch).clear(); } } bool PlaybackManager::storeEffectToMapSSG(int ch, Effect eff) { switch (eff.type) { case EffectType::Arpeggio: case EffectType::PortamentoUp: case EffectType::PortamentoDown: case EffectType::TonePortamento: case EffectType::Vibrato: case EffectType::Tremolo: case EffectType::VolumeSlide: case EffectType::Detune: case EffectType::NoteSlideUp: case EffectType::NoteSlideDown: case EffectType::NoteCut: case EffectType::TransposeDelay: case EffectType::ToneNoiseMix: case EffectType::NoisePitch: case EffectType::VolumeDelay: case EffectType::HardEnvHighPeriod: case EffectType::HardEnvLowPeriod: case EffectType::AutoEnvelope: case EffectType::RegisterAddress0: case EffectType::RegisterAddress1: case EffectType::RegisterValue: keyOnBasedEffsSSG_.at(static_cast(ch))[eff.type] = eff.value; return false; case EffectType::SpeedTempoChange: case EffectType::Groove: stepBeginBasedEffsGlobal_[eff.type] = eff.value; return false; case EffectType::NoteDelay: if (eff.value < tickCounter_.lock()->getSpeed()) { stepBeginBasedEffsSSG_.at(static_cast(ch))[EffectType::NoteDelay] = eff.value; return true; } return false; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: stepEndBasedEffsGlobal_[eff.type] = eff.value; return false; default: return false; } } void PlaybackManager::executeStoredEffectsSSG(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects for (const auto& eff : stepBeginBasedEffsSSG_.at(uch)) { switch (eff.first) { case EffectType::NoteDelay: ntDlyCntSSG_.at(uch) = eff.second; isNoteDelay = true; break; default: break; } } stepBeginBasedEffsSSG_.at(uch).clear(); // Read note on and step beginning based effects if (!isNoteDelay) { for (const auto& eff : keyOnBasedEffsSSG_.at(uch)) { switch (eff.first) { case EffectType::Arpeggio: opnaCtrl_->setArpeggioEffectSSG(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::PortamentoUp: opnaCtrl_->setPortamentoEffectSSG(ch, eff.second); break; case EffectType::PortamentoDown: opnaCtrl_->setPortamentoEffectSSG(ch, -eff.second); break; case EffectType::TonePortamento: opnaCtrl_->setPortamentoEffectSSG(ch, eff.second, true); break; case EffectType::Vibrato: opnaCtrl_->setVibratoEffectSSG(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::Tremolo: opnaCtrl_->setTremoloEffectSSG(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::VolumeSlide: { int hi = eff.second >> 4; int low = eff.second & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideSSG(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideSSG(ch, low, false); // Slide down break; } case EffectType::Detune: opnaCtrl_->setDetuneSSG(ch, eff.second - 0x80); break; case EffectType::NoteSlideUp: opnaCtrl_->setNoteSlideSSG(ch, eff.second >> 4, eff.second & 0x0f); break; case EffectType::NoteSlideDown: opnaCtrl_->setNoteSlideSSG(ch, eff.second >> 4, -(eff.second & 0x0f)); break; case EffectType::NoteCut: ntCutDlyCntSSG_.at(uch) = eff.second; break; case EffectType::TransposeDelay: tposeDlyCntSSG_.at(uch) = (eff.second & 0x70) >> 4; tposeDlyValueSSG_.at(uch) = ((eff.second & 0x80) ? -1 : 1) * (eff.second & 0x0f); break; case EffectType::ToneNoiseMix: if (-1 < eff.second && eff.second < 4) opnaCtrl_->setToneNoiseMixSSG(ch, eff.second); break; case EffectType::NoisePitch: if (-1 < eff.second && eff.second < 32) opnaCtrl_->setNoisePitchSSG(ch, eff.second); break; case EffectType::HardEnvHighPeriod: opnaCtrl_->setHardEnvelopePeriod(ch, true, eff.second); break; case EffectType::HardEnvLowPeriod: opnaCtrl_->setHardEnvelopePeriod(ch, false, eff.second); break; case EffectType::VolumeDelay: { int count = eff.second >> 8; if (count > 0) { volDlyCntSSG_.at(uch) = count; volDlyValueSSG_.at(uch) = eff.second & 0x00ff; } break; } case EffectType::AutoEnvelope: opnaCtrl_->setAutoEnvelopeSSG(ch, (eff.second >> 4) - 8, eff.second & 0x0f); break; case EffectType::RegisterAddress0: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(0, eff.second); break; } case EffectType::RegisterAddress1: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(1, eff.second); break; } case EffectType::RegisterValue: { if (-1 < eff.second) opnaCtrl_->sendRegisterValue(eff.second); break; } default: break; } } keyOnBasedEffsSSG_.at(uch).clear(); } } bool PlaybackManager::storeEffectToMapDrum(int ch, Effect eff) { switch (eff.type) { case EffectType::Pan: case EffectType::NoteCut: case EffectType::MasterVolume: case EffectType::VolumeDelay: case EffectType::RegisterAddress0: case EffectType::RegisterAddress1: case EffectType::RegisterValue: keyOnBasedEffsDrum_.at(static_cast(ch))[eff.type] = eff.value; return false; case EffectType::SpeedTempoChange: case EffectType::Groove: stepBeginBasedEffsGlobal_[eff.type] = eff.value; return false; case EffectType::NoteDelay: if (eff.value < tickCounter_.lock()->getSpeed()) { stepBeginBasedEffsDrum_.at(static_cast(ch))[EffectType::NoteDelay] = eff.value; return true; } return false; case EffectType::PositionJump: case EffectType::SongEnd: case EffectType::PatternBreak: stepEndBasedEffsGlobal_[eff.type] = eff.value; return false; default: return false; } } void PlaybackManager::executeStoredEffectsDrum(int ch) { size_t uch = static_cast(ch); bool isNoteDelay = false; // Read step beginning based effects for (const auto& eff : stepBeginBasedEffsDrum_.at(uch)) { switch (eff.first) { case EffectType::NoteDelay: ntDlyCntDrum_.at(uch) = eff.second; isNoteDelay = true; break; default: break; } } stepBeginBasedEffsDrum_.at(uch).clear(); // Read key on and step beginning based effects if (!isNoteDelay) { for (const auto& eff : keyOnBasedEffsDrum_.at(uch)) { switch (eff.first) { case EffectType::Pan: if (-1 < eff.second && eff.second < 4) opnaCtrl_->setPanDrum(ch, eff.second); break; case EffectType::NoteCut: ntCutDlyCntDrum_.at(uch) = eff.second; break; case EffectType::MasterVolume: if (-1 < eff.second && eff.second < 64) opnaCtrl_->setMasterVolumeDrum(eff.second); break; case EffectType::VolumeDelay: { int count = eff.second >> 8; if (count > 0) { volDlyCntDrum_.at(uch) = count; volDlyValueDrum_.at(uch) = eff.second & 0x00ff; } break; } case EffectType::RegisterAddress0: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(0, eff.second); break; } case EffectType::RegisterAddress1: { if (-1 < eff.second && eff.second < 0x6c) opnaCtrl_->sendRegisterAddress(1, eff.second); break; } case EffectType::RegisterValue: { if (-1 < eff.second) opnaCtrl_->sendRegisterValue(eff.second); break; } default: break; } } keyOnBasedEffsDrum_.at(uch).clear(); } } bool PlaybackManager::effPositionJump(int nextOrder) { if (nextOrder < static_cast(getOrderSize(curSongNum_))) { nextReadOrder_ = nextOrder; nextReadStep_ = 0; return true; } return false; } void PlaybackManager::effSongEnd() { nextReadOrder_ = -1; nextReadStep_ = -1; } bool PlaybackManager::effPatternBreak(int nextStep) { if (playOrderNum_ == static_cast(getOrderSize(curSongNum_)) - 1 && nextStep < static_cast(getPatternSizeFromOrderNumber(curSongNum_, 0))) { nextReadOrder_ = 0; nextReadStep_ = nextStep; return true; } else if (nextStep < static_cast(getPatternSizeFromOrderNumber(curSongNum_, playOrderNum_ + 1))) { nextReadOrder_ = playOrderNum_ + 1; nextReadStep_ = nextStep; return true; } return false; } void PlaybackManager::effSpeedChange(int speed) { tickCounter_.lock()->setSpeed(speed ? speed : 1); tickCounter_.lock()->setGrooveTrigger(GrooveTrigger::Invalid); } void PlaybackManager::effTempoChange(int tempo) { tickCounter_.lock()->setTempo(tempo); tickCounter_.lock()->setGrooveTrigger(GrooveTrigger::Invalid); } void PlaybackManager::effGrooveChange(int num) { tickCounter_.lock()->setGroove(mod_.lock()->getGroove(num).getSequence()); tickCounter_.lock()->setGrooveTrigger(GrooveTrigger::ValidByLocal); } void PlaybackManager::tickProcess(int rest) { if (!(playState_ & 0x02)) return; // When it has not read first step updateDelayEventCounts(); auto& song = mod_.lock()->getSong(curSongNum_); for (auto& attrib : songStyle_.trackAttribs) { auto& curStep = song.getTrack(attrib.number) .getPatternFromOrderNumber(playOrderNum_).getStep(playStepNum_); int ch = attrib.channelInSource; switch (attrib.source) { case SoundSource::FM: checkFMDelayEventsInTick(curStep, ch); break; case SoundSource::SSG: checkSSGDelayEventsInTick(curStep, ch); break; case SoundSource::DRUM: checkDrumDelayEventsInTick(curStep, ch); break; } if (rest == 1 && nextReadOrder_ != -1 && attrib.source == SoundSource::FM) { // Channel envelope reset before next key on auto& step = song.getTrack(attrib.number) .getPatternFromOrderNumber(nextReadOrder_).getStep(nextReadStep_); for (int i = 0; i < 4; ++i) { auto&& eff = Effect::makeEffectData(attrib.source, step.getEffectID(i), step.getEffectValue(i)); if (eff.type == EffectType::NoteDelay && eff.value > 0) { // Note delay check opnaCtrl_->tickEvent(attrib.source, ch); return; } } envelopeResetEffectFM(step, ch); } else { opnaCtrl_->tickEvent(attrib.source, ch); } } opnaCtrl_->updateRegisterStates(); } void PlaybackManager::checkFMDelayEventsInTick(Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntFM_.at(uch)) opnaCtrl_->setTemporaryVolumeFM(ch, volDlyValueFM_.at(uch)); // Check note cut if (!ntCutDlyCntFM_.at(uch)) opnaCtrl_->keyOffFM(ch); // Check transpose delay if (!tposeDlyCntFM_.at(uch)) opnaCtrl_->setTransposeEffectFM(ch, tposeDlyValueFM_.at(uch)); // Check note delay and envelope reset checkFMNoteDelayAndEnvelopeReset(step, ch); } void PlaybackManager::checkFMNoteDelayAndEnvelopeReset(Step& step, int ch) { int cnt = ntDlyCntFM_.at(static_cast(ch)); if (!cnt) { executeFMStepEvents(step, ch, true); } else if (cnt == 1) { // Channel envelope reset before next key on envelopeResetEffectFM(step, ch); } } void PlaybackManager::envelopeResetEffectFM(Step& step, int ch) { int n = step.getNoteNumber(); if ((n >= 0 || n < -2) && opnaCtrl_->enableFMEnvelopeReset(ch)) { // Key on or echo buffer access for (int i = 0; i < 4; ++i) { auto&& eff = Effect::makeEffectData( // "SoundSource::FM" is dummy SoundSource::FM, step.getEffectID(i), step.getEffectValue(i)); if (eff.type == EffectType::TonePortamento) { if (eff.value) opnaCtrl_->tickEvent(SoundSource::FM, ch); else opnaCtrl_->resetFMChannelEnvelope(ch); return; } } if (opnaCtrl_->isTonePortamentoFM(ch)) opnaCtrl_->tickEvent(SoundSource::FM, ch); else opnaCtrl_->resetFMChannelEnvelope(ch); } else { opnaCtrl_->tickEvent(SoundSource::FM, ch); } } void PlaybackManager::checkSSGDelayEventsInTick(Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntSSG_.at(uch)) opnaCtrl_->setTemporaryVolumeSSG(ch, volDlyValueSSG_.at(uch)); // Check note cut if (!ntCutDlyCntSSG_.at(uch)) opnaCtrl_->keyOffSSG(ch); // Check transpose delay if (!tposeDlyCntSSG_.at(uch)) opnaCtrl_->setTransposeEffectSSG(ch, tposeDlyValueSSG_.at(uch)); // Check note delay if (!ntDlyCntSSG_.at(uch)) executeSSGStepEvents(step, ch, true); } void PlaybackManager::checkDrumDelayEventsInTick(Step& step, int ch) { size_t uch = static_cast(ch); // Check volume delay if (!volDlyCntDrum_.at(uch)) opnaCtrl_->setTemporaryVolumeDrum(ch, volDlyValueDrum_.at(uch)); // Check note cut if (!ntCutDlyCntDrum_.at(uch)) opnaCtrl_->setKeyOnFlagDrum(ch); // Check note delay if (!ntDlyCntDrum_.at(uch)) executeDrumStepEvents(step, ch, true); } void PlaybackManager::clearEffectMaps() { stepBeginBasedEffsGlobal_.clear(); stepEndBasedEffsGlobal_.clear(); for (auto& map : keyOnBasedEffsFM_) map.clear(); for (auto& map : stepBeginBasedEffsFM_) map.clear(); for (auto& map : keyOnBasedEffsSSG_) map.clear(); for (auto& map : stepBeginBasedEffsSSG_) map.clear(); for (auto& map : keyOnBasedEffsDrum_) map.clear(); for (auto& map : stepBeginBasedEffsDrum_) map.clear(); } void PlaybackManager::clearNoteDelayCounts() { std::fill(ntDlyCntFM_.begin(), ntDlyCntFM_.end(), -1); std::fill(ntDlyCntSSG_.begin(), ntDlyCntSSG_.end(), -1); std::fill(ntDlyCntDrum_.begin(), ntDlyCntDrum_.end(), -1); } void PlaybackManager::clearDelayBeyondStepCounts() { std::fill(ntCutDlyCntFM_.begin(), ntCutDlyCntFM_.end(), -1); std::fill(volDlyCntFM_.begin(), volDlyCntFM_.end(), -1); std::fill(volDlyValueFM_.begin(), volDlyValueFM_.end(), -1); std::fill(tposeDlyCntFM_.begin(), tposeDlyCntFM_.end(), -1); std::fill(tposeDlyValueFM_.begin(), tposeDlyValueFM_.end(), 0); std::fill(ntCutDlyCntSSG_.begin(), ntCutDlyCntSSG_.end(), -1); std::fill(volDlyCntSSG_.begin(), volDlyCntSSG_.end(), -1); std::fill(volDlyValueSSG_.begin(), volDlyValueSSG_.end(), -1); std::fill(tposeDlyCntSSG_.begin(), tposeDlyCntSSG_.end(), -1); std::fill(tposeDlyValueSSG_.begin(), tposeDlyValueSSG_.end(), 0); std::fill(ntCutDlyCntDrum_.begin(), ntCutDlyCntDrum_.end(), -1); std::fill(volDlyCntDrum_.begin(), volDlyCntDrum_.end(), -1); std::fill(volDlyValueDrum_.begin(), volDlyValueDrum_.end(), -1); } void PlaybackManager::clearFMDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntCutDlyCntFM_.at(uch) = -1; volDlyCntFM_.at(uch) = -1; volDlyValueFM_.at(uch) = -1; tposeDlyCntFM_.at(uch) = -1; tposeDlyValueFM_.at(uch) = 0; } void PlaybackManager::clearSSGDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntCutDlyCntSSG_.at(uch) = -1; volDlyCntSSG_.at(uch) = -1; volDlyValueSSG_.at(uch) = -1; tposeDlyCntSSG_.at(uch) = -1; tposeDlyValueSSG_.at(uch) = 0; } void PlaybackManager::clearDrumDelayBeyondStepCounts(int ch) { size_t uch = static_cast(ch); ntCutDlyCntDrum_.at(uch) = -1; volDlyCntDrum_.at(uch) = -1; volDlyValueDrum_.at(uch) = -1; } void PlaybackManager::updateDelayEventCounts() { auto f = [](int x) { return (x == -1) ? x : --x; }; std::transform(ntDlyCntFM_.begin(), ntDlyCntFM_.end(), ntDlyCntFM_.begin(), f); std::transform(ntDlyCntSSG_.begin(), ntDlyCntSSG_.end(), ntDlyCntSSG_.begin(), f); std::transform(ntDlyCntDrum_.begin(), ntDlyCntDrum_.end(), ntDlyCntDrum_.begin(), f); std::transform(ntCutDlyCntFM_.begin(), ntCutDlyCntFM_.end(), ntCutDlyCntFM_.begin(), f); std::transform(ntCutDlyCntSSG_.begin(), ntCutDlyCntSSG_.end(), ntCutDlyCntSSG_.begin(), f); std::transform(ntCutDlyCntDrum_.begin(), ntCutDlyCntDrum_.end(), ntCutDlyCntDrum_.begin(), f); std::transform(volDlyCntFM_.begin(), volDlyCntFM_.end(), volDlyCntFM_.begin(), f); std::transform(volDlyCntSSG_.begin(), volDlyCntSSG_.end(), volDlyCntSSG_.begin(), f); std::transform(volDlyCntDrum_.begin(), volDlyCntDrum_.end(), volDlyCntDrum_.begin(), f); std::transform(tposeDlyCntFM_.begin(), tposeDlyCntFM_.end(), tposeDlyCntFM_.begin(), f); std::transform(tposeDlyCntSSG_.begin(), tposeDlyCntSSG_.end(), tposeDlyCntSSG_.begin(), f); } void PlaybackManager::checkPlayPosition(int maxStepSize) { if (isPlaySong() && playStepNum_ >= maxStepSize) { playStepNum_ = maxStepSize - 1; findNextStep(); } } void PlaybackManager::setChannelRetrieving(bool enabled) { isRetrieveChannel_ = enabled; } void PlaybackManager::retrieveChannelStates() { size_t fmch = getFMChannelCount(songStyle_.type); std::vector tonesCntFM(fmch, 0), tonesCntSSG(3, 0); std::vector> toneFM(fmch), toneSSG(3); for (size_t i = 0; i < fmch; ++i) { toneFM.at(i) = std::vector(3, -1); } for (size_t i = 0; i < 3; ++i) { toneSSG.at(i) = std::vector(3, -1); } std::vector isSetInstFM(fmch, false), isSetVolFM(fmch, false), isSetArpFM(fmch, false); std::vector isSetPrtFM(fmch, false), isSetVibFM(fmch, false), isSetTreFM(fmch, false); std::vector isSetPanFM(fmch, false), isSetVolSldFM(fmch, false), isSetDtnFM(fmch, false); std::vector isSetFBCtrlFM(fmch, false), isSetTLCtrlFM(fmch, false), isSetMLCtrlFM(fmch, false); std::vector isSetARCtrlFM(fmch, false), isSetDRCtrlFM(fmch, false), isSetRRCtrlFM(fmch, false); std::vector isSetBrightFM(fmch, false); std::vector isSetInstSSG(3, false), isSetVolSSG(3, false), isSetArpSSG(3, false), isSetPrtSSG(3, false); std::vector isSetVibSSG(3, false), isSetTreSSG(3, false), isSetVolSldSSG(3, false), isSetDtnSSG(3, false); std::vector isSetTNMixSSG(3, false); std::vector isSetVolDrum(6, false), isSetPanDrum(6, false); bool isSetMVolDrum = false; bool isSetNoisePitchSSG = false; bool isSetHardEnvPeriodHighSSG = false; bool isSetHardEnvPeriodLowSSG = false; bool isSetAutoEnvSSG = false; /// bit0: step /// bit1: tempo /// bit2: groove uint8_t speedStates = 0; int o = playOrderNum_; int s = playStepNum_; bool isPrevPos = false; Song& song = mod_.lock()->getSong(curSongNum_); while (true) { for (auto it = songStyle_.trackAttribs.rbegin(), e = songStyle_.trackAttribs.rend(); it != e; ++it) { Step& step = song.getTrack(it->number).getPatternFromOrderNumber(o).getStep(s); int ch = it->channelInSource; size_t uch = static_cast(ch); switch (it->source) { case SoundSource::FM: { // Volume int vol = step.getVolume(); if (!isSetVolFM[uch] && 0 <= vol && vol < 0x80) { isSetVolFM[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeFM(ch, step.getVolume()); } // Instrument if (!isSetInstFM[uch] && step.getInstrumentNumber() != -1) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) { isSetInstFM[uch] = true; if (isPrevPos) opnaCtrl_->setInstrumentFM(ch, inst); } } // Effects for (int i = 3; i > -1; --i) { Effect eff = Effect::makeEffectData(SoundSource::FM, step.getEffectID(i), step.getEffectValue(i)); switch (eff.type) { case EffectType::Arpeggio: if (!isSetArpFM[uch]) { isSetArpFM[uch] = true; if (isPrevPos) opnaCtrl_->setArpeggioEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::PortamentoUp: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, eff.value); } break; case EffectType::PortamentoDown: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, -eff.value); } break; case EffectType::TonePortamento: if (!isSetPrtFM[uch]) { isSetPrtFM[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectFM(ch, eff.value, true); } break; case EffectType::Vibrato: if (!isSetVibFM[uch]) { isSetVibFM[uch] = true; if (isPrevPos) opnaCtrl_->setVibratoEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Tremolo: if (!isSetTreFM[uch]) { isSetTreFM[uch] = true; if (isPrevPos) opnaCtrl_->setTremoloEffectFM(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Pan: if (-1 < eff.value && eff.value < 4 && !isSetPanFM[uch]) { isSetPanFM[uch] = true; if (isPrevPos) opnaCtrl_->setPanFM(ch, eff.value); } break; case EffectType::VolumeSlide: if (!isSetVolSldFM[uch]) { isSetVolSldFM[uch] = true; if (isPrevPos) { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideFM(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideFM(ch, low, false); // Slide down } } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20 && !(speedStates & 0x1)) { // Speed change speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::Detune: if (!isSetDtnFM[uch]) { isSetDtnFM[uch] = true; if (isPrevPos) opnaCtrl_->setDetuneFM(ch, eff.value - 0x80); } break; case EffectType::FBControl: if (!isSetFBCtrlFM[uch]) { isSetFBCtrlFM[uch] = true; if (isPrevPos) { if (-1 < eff.value && eff.value < 8) opnaCtrl_->setFBControlFM(ch, eff.value); } } break; case EffectType::TLControl: if (!isSetTLCtrlFM[uch]) { isSetTLCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 128) opnaCtrl_->setTLControlFM(ch, op - 1, val); } } break; case EffectType::MLControl: if (!isSetMLCtrlFM[uch]) { isSetMLCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setMLControlFM(ch, op - 1, val); } } break; case EffectType::ARControl: if (!isSetARCtrlFM[uch]) { isSetARCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setARControlFM(ch, op - 1, val); } } break; case EffectType::DRControl: if (!isSetDRCtrlFM[uch]) { isSetDRCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 8; int val = eff.value & 0x00ff; if (0 < op && op < 5 && -1 < val && val < 32) opnaCtrl_->setDRControlFM(ch, op - 1, val); } } break; case EffectType::RRControl: if (!isSetRRCtrlFM[uch]) { isSetRRCtrlFM[uch] = true; if (isPrevPos) { int op = eff.value >> 4; int val = eff.value & 0x0f; if (0 < op && op < 5 && -1 < val && val < 16) opnaCtrl_->setRRControlFM(ch, op - 1, val); } } break; case EffectType::Brightness: if (!isSetBrightFM[uch]) { isSetBrightFM[uch] = true; if (isPrevPos) { if (0 < eff.value) opnaCtrl_->setBrightnessFM(ch, eff.value - 0x80); } } break; default: break; } } // Tone int t = step.getNoteNumber(); if (isPrevPos && t != -1 && t != -2) { --tonesCntFM[uch]; for (auto it2 = toneFM[uch].rbegin(); it2 != toneFM[uch].rend(); ++it2) { if (*it2 == -1 || *it2 == tonesCntFM[uch]) { if (t >= 0) { *it2 = t; } else if (t < -2) { *it2 = tonesCntFM[uch] - t + 2; } break; } } } break; } case SoundSource::SSG: { // Volume int vol = step.getVolume(); if (!isSetVolSSG[uch] && 0 <= vol && vol < 0x10) { isSetVolSSG[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeSSG(ch, vol); } // Instrument if (!isSetInstSSG[uch] && step.getInstrumentNumber() != -1) { if (auto inst = std::dynamic_pointer_cast( instMan_.lock()->getInstrumentSharedPtr(step.getInstrumentNumber()))) { isSetInstSSG[uch] = true; if (isPrevPos) opnaCtrl_->setInstrumentSSG(ch, inst); } } // Effects for (int i = 3; i > -1; --i) { Effect eff = Effect::makeEffectData(SoundSource::SSG, step.getEffectID(i), step.getEffectValue(i)); switch (eff.type) { case EffectType::Arpeggio: if (!isSetArpSSG[uch]) { isSetArpSSG[uch] = true; if (isPrevPos) opnaCtrl_->setArpeggioEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::PortamentoUp: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, eff.value); } break; case EffectType::PortamentoDown: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, -eff.value); } break; case EffectType::TonePortamento: if (!isSetPrtSSG[uch]) { isSetPrtSSG[uch] = true; if (isPrevPos) opnaCtrl_->setPortamentoEffectSSG(ch, eff.value, true); } break; case EffectType::Vibrato: if (!isSetVibSSG[uch]) { isSetVibSSG[uch] = true; if (isPrevPos) opnaCtrl_->setVibratoEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::Tremolo: if (!isSetTreSSG[uch]) { isSetTreSSG[uch] = true; if (isPrevPos) opnaCtrl_->setTremoloEffectSSG(ch, eff.value >> 4, eff.value & 0x0f); } break; case EffectType::VolumeSlide: if (!isSetVolSldSSG[uch]) { isSetVolSldSSG[uch] = true; if (isPrevPos) { int hi = eff.value >> 4; int low = eff.value & 0x0f; if (hi && !low) opnaCtrl_->setVolumeSlideSSG(ch, hi, true); // Slide up else if (!hi) opnaCtrl_->setVolumeSlideSSG(ch, low, false); // Slide down } } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20 && !(speedStates & 0x1)) { // Speed change speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::Detune: if (!isSetDtnSSG[uch]) { isSetDtnSSG[uch] = true; if (isPrevPos) opnaCtrl_->setDetuneSSG(ch, eff.value - 0x80); } break; case EffectType::ToneNoiseMix: if (-1 < eff.value && eff.value < 4 && !isSetTNMixSSG[uch]) { isSetTNMixSSG[uch] = true; if (isPrevPos) opnaCtrl_->setToneNoiseMixSSG(ch, eff.value); } break; case EffectType::NoisePitch: if (-1 < eff.value && eff.value < 32 && !isSetNoisePitchSSG) { isSetNoisePitchSSG = true; if (isPrevPos) opnaCtrl_->setNoisePitchSSG(ch, eff.value); } break; case EffectType::HardEnvHighPeriod: if (!isSetHardEnvPeriodHighSSG) { isSetHardEnvPeriodHighSSG = true; if (isPrevPos) opnaCtrl_->setHardEnvelopePeriod(ch, true, eff.value); } break; case EffectType::HardEnvLowPeriod: if (!isSetHardEnvPeriodLowSSG) { isSetHardEnvPeriodLowSSG = true; if (isPrevPos) opnaCtrl_->setHardEnvelopePeriod(ch, false, eff.value); } break; case EffectType::AutoEnvelope: if (!isSetAutoEnvSSG) { isSetAutoEnvSSG = true; if (isPrevPos) opnaCtrl_->setAutoEnvelopeSSG(ch, (eff.value >> 4) - 8, eff.value & 0x0f); } break; default: break; } } // Tone int t = step.getNoteNumber(); if (isPrevPos && t != -1 && t != -2) { --tonesCntSSG[uch]; for (auto it2 = toneSSG[uch].rbegin(); it2 != toneSSG[uch].rend(); ++it2) { if (*it2 == -1 || *it2 == tonesCntSSG[uch]) { if (t >= 0) { *it2 = t; } else if (t < -2) { *it2 = tonesCntSSG[uch] - t + 2; } break; } } } break; } case SoundSource::DRUM: { // Volume int vol = step.getVolume(); if (!isSetVolDrum[uch] && 0 <= vol && vol < 0x20) { isSetVolDrum[uch] = true; if (isPrevPos) opnaCtrl_->setVolumeDrum(ch, vol); } // Effects for (int i = 3; i > -1; --i) { Effect eff = Effect::makeEffectData(SoundSource::DRUM, step.getEffectID(i), step.getEffectValue(i)); switch (eff.type) { case EffectType::Pan: if (-1 < eff.value && eff.value < 4 && !isSetPanDrum[uch]) { isSetPanDrum[uch] = true; if (isPrevPos) opnaCtrl_->setPanDrum(ch, eff.value); } break; case EffectType::SpeedTempoChange: if (!(speedStates & 0x4)) { if (eff.value < 0x20 && !(speedStates & 0x1)) { // Speed change speedStates |= 0x1; if (isPrevPos) effSpeedChange(eff.value); } else if (!(speedStates & 0x2)) { // Tempo change speedStates |= 0x2; if (isPrevPos) effTempoChange(eff.value); } } break; case EffectType::Groove: if (eff.value < static_cast(mod_.lock()->getGrooveCount()) && !speedStates) { speedStates |= 0x4; if (isPrevPos) effGrooveChange(eff.value); } break; case EffectType::MasterVolume: if (-1 < eff.value && eff.value < 64 && !isSetMVolDrum) { isSetMVolDrum = true; if (isPrevPos) opnaCtrl_->setMasterVolumeDrum(eff.value); } break; default: break; } } break; } } } // Move position isPrevPos = true; if (--s < 0) { if (--o < 0) break; s = static_cast(getPatternSizeFromOrderNumber(curSongNum_, o)) - 1; } } // Echo & sequence reset for (size_t ch = 0; ch < fmch; ++ch) { for (size_t i = 0; i < 3; ++i) { if (toneFM.at(ch).at(i) >= 0) { std::pair octNote = noteNumberToOctaveAndNote(toneFM[ch][i]); opnaCtrl_->updateEchoBufferFM(static_cast(ch), octNote.first, octNote.second, 0); } } opnaCtrl_->haltSequencesFM(static_cast(ch)); } for (size_t ch = 0; ch < 3; ++ch) { for (size_t i = 0; i < 3; ++i) { if (toneSSG.at(ch).at(i) >= 0) { std::pair octNote = noteNumberToOctaveAndNote(toneSSG[ch][i]); opnaCtrl_->updateEchoBufferSSG(static_cast(ch), octNote.first, octNote.second, 0); } } opnaCtrl_->haltSequencesSSG(static_cast(ch)); } } size_t PlaybackManager::getOrderSize(int songNum) const { return mod_.lock()->getSong(songNum).getOrderSize(); } size_t PlaybackManager::getPatternSizeFromOrderNumber(int songNum, int orderNum) const { return mod_.lock()->getSong(songNum).getPatternSizeFromOrderNumber(orderNum); } BambooTracker-0.3.5/BambooTracker/playback.hpp000066400000000000000000000100341362177441300212520ustar00rootroot00000000000000#pragma once #include #include #include #include #include "opna_controller.hpp" #include "instruments_manager.hpp" #include "module.hpp" #include "tick_counter.hpp" #include "effect.hpp" enum class PlaybackState { PlaySong, PlayFromStart, PlayPattern, PlayFromCurrentStep, Stop }; /// Divede playback routine from main class BambooTracker class PlaybackManager { public: PlaybackManager(std::shared_ptr opnaCtrl, std::weak_ptr instMan, std::weak_ptr tickCounter, std::weak_ptr mod, bool isRetrieveChannel); void setSong(std::weak_ptr mod, int songNum); // Play song void startPlaySong(int order); void startPlayFromStart(); void startPlayPattern(int order); void startPlayFromCurrentStep(int order, int step); void stopPlaySong(); bool isPlaySong() const; PlaybackState getPlaybackState() const; int getPlayingOrderNumber() const; int getPlayingStepNumber() const; // Stream events /// 0<: Tick /// 0: Step /// -1: Stop int streamCountUp(); void checkPlayPosition(int maxStepSize); void setChannelRetrieving(bool enabled); private: std::shared_ptr opnaCtrl_; std::weak_ptr instMan_; std::weak_ptr tickCounter_; std::weak_ptr mod_; std::mutex mutex_; int curSongNum_; SongStyle songStyle_; int playOrderNum_, playStepNum_; int nextReadOrder_, nextReadStep_; /// High nibble - play type /// bit 4: If high, loop pattern /// Low nibble - read state /// bit 0: playing /// bit 1: have read first step data unsigned int playState_; PlaybackState managerState_; // Play song bool isFindNextStep_; void startPlay(); void stopPlay(); bool stepDown(); void findNextStep(); void checkValidPosition(); void stepProcess(); std::vector isNoteDelayFM_, isNoteDelaySSG_, isNoteDelayDrum_; void executeFMStepEvents(Step& step, int ch, bool calledByNoteDelay = false); void executeSSGStepEvents(Step& step, int ch, bool calledByNoteDelay = false); void executeDrumStepEvents(Step& step, int ch, bool calledByNoteDelay = false); std::unordered_map stepBeginBasedEffsGlobal_, stepEndBasedEffsGlobal_; std::vector> keyOnBasedEffsFM_, stepBeginBasedEffsFM_; std::vector> keyOnBasedEffsSSG_, stepBeginBasedEffsSSG_; std::vector> keyOnBasedEffsDrum_, stepBeginBasedEffsDrum_; bool executeStoredEffectsGlobal(); bool storeEffectToMapFM(int ch, Effect eff); void executeStoredEffectsFM(int ch); bool storeEffectToMapSSG(int ch, Effect eff); void executeStoredEffectsSSG(int ch); bool storeEffectToMapDrum(int ch, Effect eff); void executeStoredEffectsDrum(int ch); bool effPositionJump(int nextOrder); void effSongEnd(); bool effPatternBreak(int nextStep); void effSpeedChange(int speed); void effTempoChange(int tempo); void effGrooveChange(int num); void tickProcess(int rest); void checkFMDelayEventsInTick(Step& step, int ch); void checkFMNoteDelayAndEnvelopeReset(Step& step, int ch); void envelopeResetEffectFM(Step& step, int ch); void checkSSGDelayEventsInTick(Step& step, int ch); void checkDrumDelayEventsInTick(Step& step, int ch); std::vector ntDlyCntFM_, ntCutDlyCntFM_, volDlyCntFM_; std::vector ntDlyCntSSG_, ntCutDlyCntSSG_, volDlyCntSSG_; std::vector ntDlyCntDrum_, ntCutDlyCntDrum_, volDlyCntDrum_; std::vector volDlyValueFM_, volDlyValueSSG_, volDlyValueDrum_; std::vector tposeDlyCntFM_, tposeDlyCntSSG_; std::vector tposeDlyValueFM_, tposeDlyValueSSG_; void clearEffectMaps(); void clearNoteDelayCounts(); void clearDelayBeyondStepCounts(); void clearFMDelayBeyondStepCounts(int ch); void clearSSGDelayBeyondStepCounts(int ch); void clearDrumDelayBeyondStepCounts(int ch); void updateDelayEventCounts(); bool isRetrieveChannel_; void retrieveChannelStates(); size_t getOrderSize(int songNum) const; size_t getPatternSizeFromOrderNumber(int songNum, int orderNum) const; }; BambooTracker-0.3.5/BambooTracker/res/000077500000000000000000000000001362177441300175465ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/res/doc/000077500000000000000000000000001362177441300203135ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/res/doc/keyboard_shortcuts.html000066400000000000000000000076251362177441300251310ustar00rootroot00000000000000 Keyboard shortcuts

General

Key Command
Ctrl+N Create new module
Ctrl+O Open module
Ctrl+S Save module
Ctrl+P Open module property dialog
Ctrl+I Open current instrument editor
Return Play/stop song
F1 Show effect list dialog
F5 Play from start
F6 Play pattern
F7 Play from current position
F8 Stop song
Space Toggle jam/edit mode
Alt+L Focus on instrument list
Alt+O Focus on order list
Alt+P Focus on pattern editor
F12 Kill sound

Instrument list

Key Command
Insert Add instrument
Delete Remove instrument
Ctrl+I Open current instrument editor

Order list

Key Command
Ctrl+C Copy
Ctrl+V Paste
Ctrl+A Select track/all
Ctrl+D Duplicate order
Alt+D Clone order
Home Jump to first order
End Jump to last order
PageUp Jump to upper oder
PageDown Jump to lower oder
Insert Insert order below
Delete Delete order
Escape Deselect

Pattern editor

Key Command
Ctrl+C Copy
Ctrl+X Cut
Ctrl+V Paste
Ctrl+M Paste and mix
Ctrl+A Select track/all
Ctrl+G Interpolate
Ctrl+R Reverse
Ctrl+F1 Decrease note
Ctrl+F2 Increase note
Ctrl+F3 Decrease octave
Ctrl+F4 Increase octave
Alt+F9 Toggle track
Alt+F10 Solo track
Alt+S Replace instrument
Tab Jump to right track
BackTab Jump to left track
Home Jump to first step
End Jump to last step
PageUp Jump to upper step
PageDown Jump to lower step
Ctrl+Up Jump to upper 1st highlighted step
Ctrl+Down Jump to lower 1st highlighted step
Insert Insert step
BackSpace Delete the step above
Delete Delete commands
Escape Deselection
- Key off
* (numpad) Increase octave/echo buffer number
/ (numpad) Decrease octave/echo buffer number
^ Echo buffer access
BambooTracker-0.3.5/BambooTracker/res/icon/000077500000000000000000000000001362177441300204765ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/res/icon/BambooTracker.ico000066400000000000000000003066561362177441300237250ustar00rootroot00000000000000 -V (N.@@ (Bv6  x hFPNG  IHDR\rfsRGBgAMA a pHYsod-IDATx^эfrA.ȅr] ¸ dK. r -d܍$`\pS}e/iM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚ?~jiM&TZse*㗡DJknL57QҚϟ?5=Ǐ5'giIYZsz֜5'giIYZЛ,9I=KkNRҚԳ$,9I=Kkz5'giIYZsz֜5'giBoг$,9I=KkNRҚԳ$,YMz֜5'giIYZsz֜5 @ҚԳ$,9I=KkNRҚԳf7]?YZsz֜5'giIYZsz,=KkNRҚԳ$,9I=KkNRҚthh57QҚ(S?럿?/i&#~2tiJknL57QңLK4?~457QҚ(S}&zG_Ɉ ]Қ(SiM>=ң/dď_.MiM&Tzt}їf2/C&TZse*=DK3㗡KSZse*2gG_zХ)2DJ3ѣ/=LFeҔDJknLGSztGP/ۯi&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&#~2tiJknL57Qң/=ң^fї}i&?+I?~%9I=KkNRҚԳ$,YQ~NRң^(sMz֜5'giIYZУ+=ړԳ\z5'giIYZsz,J$,=2t}giIYZsz֜5 =ң=I=Kz̥7]?YZsz֜5'giBhORң^(sMz֜5'giIYZУ+=ړԳ\z5'giIYZsz,J$,=2t}giIYZsz֜5 =ң=I=Kz̥7]?럟ڿ_%e.EiMt;sz\o5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5L4KoGQZse*yΜ;Wz5m4jֿ~4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3kj)tih , 4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3kj)tih , 4RQDJwtgJwn;Y5 4jZj)M(JknL;Y3;Pҝ,LFZ ]B5KC- &z57Qҝ,ݙҝ[fNdi&#~|MC-. mZ|=Қ(SNL-Tt'Kw4?BPPKC-eEiMt'KwtY;YɈ_PKKC[fi2D&T;Ss ,ҝ,dďiХ-T4PKoGQZse*ҝ)ݹjdNf24RYji7ѣ(2dΔB5Kwt'K3_PK. $, m'㗴$,9I=Kk;uzBKo>'җ֜5'giBwtNRҝ[(sM$]ҚԳ$,YNIYs e.2K_Zsz֜5 ݉ҝ:I=Kwn̥7]?PtKkNRҚԳf;QS'g-|.}iIYZsz,t'Jw$,ݹ2t}|@Oҥ/9I=KkNRҚDN;Pқ(I5'giIYZН(ݩԳt\zcKk|}~Ak5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5* $, 4RtKkz5'LJgRI)SgN̥;Y_SPNRPKC-e>IfGQZsΤt&2zIY\5m4j.Z\B=z5=μ4|=2B5K{~PЙ5 4jZj)sfhO \ї֔T:L0jTTs ,ACg64RYjaŚ=-sG_ZSSK3y'S)S-Teو_PKKC[fq2kPυ}iMiO3/3fhOL;PҞ9tf#~|MC-. mšZ\B=z5=μ4|=2B5K{~PЙ5 4jZj)sfhO \ї֔T:L0jTTs ,ACg64RYjaŚ=-sG_ZSSK3y'S)S-Teو_PKKC[fq2kPυ}iMiO3/3fhOL;PҞ9tf#~|MC-. mš5 |G_:R$yqOL3)\fiIGZ ]B5C- AkB=BtfI:BJgRPҞ0ҙ5 4jZքz.34}Jt=2Τs=?`&3kj)tih ,0 \gh =ҙ2?`'̋{ e*IB5K{~LJg>4RYjaZ`z3+e~OҙT:Rυj|ďiХ-T8P&s/KgV=3/)t& ,3)_PKKC[fq>hM=C3_їά{>Ig^S(SLJ=YfR:?BPP C}КP{fУ/Y)|μPҙz.T̤t#~|MC-. mš;i'gRBg =KwtgJ{Zf1S!SGZ ]B5C- wLOң/Τl=zdΔPbBҙ5 4jZ酪G_Ia ق{*,ҝ)iLL3kj)tih ,0I3=I8ž:TY;SB5 Jg>4RYjafz}q&=/tf гt'Kwj32|ďiХ-T8P'$=L {^SgNLiO ,f*d*_PKKC[fqNIzřЙ-Bҝ,ݙҞYTT:?BPP C4ӓ3)y3[pO;Y3=-Tt擟?+4M~z>`h e>Ǐ5'iK[ Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ(Iz5'iG_z Dw"$|О:қ?FKqz4RRfLKgZZSyz\3k UIYji7ѣ/3љδ$,t'Kg>4Գ4PKoG_g3-iiMIYsN|ď)Ti('gi2D4DgZ:ҚRϓԳҝ,_SPNRPKC-e}iδt5'giϥ;Y:?PZ|=L?iLKkJ=ORҞKwt#~|MJC9I=KC- &z~&:ҙ֔z=dGBrzZj)MK3Lt3-)I$,4Ig2ARϓA{LKo>'ї֜ęf;L&Tyz>hiM$=Қ8'J=ORy3-2G_Zsg3 SI=t7]?PKkNB3t&d*h $|Оz7]?QОNRy=B5C{:I=dMGT3A{^fhO'PОNRy7Yz $|ОI=/T3A{Mt}|D5C{:I=jtz>h $|Оz7]?QОNRy=B5C{:I=dM#sC#NRҡZYIYTTYIYTTYIYTTz#~|MJC9I=KC- TԳTԳTԳ&GBrzZjfiO'g)S)SfiO'g)S)SfiO'g)S)SM5* $, 4RҞNRRRRҞNRRRRҞNRRRқk UIYji=LL=LL=LL794Գ4PK5K{:I=KJJ5K{:I=KJJ5K{:I=KJJorď)Ti('gijtz22jtz22jtz22_SPNRPKC-,$,e*e*,$,e*e*,$,e*e*?MBP?_ZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz#~|MC-. mšZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz#~|MC-. mšZTYB5 JJ=K{^f1S!S)Sgi ,f*d*?BPP C-e*,yLLL=/TԳj32_PKKC[fq2zPbBRRҞYTTTYB5 JorďiХ-T8PKJ=K{^f1S!S)Sgi ,f*d*e*,yLL794RYjaL=/TԳj322zPbBқkj)tih ,0RRҞYTTTYB5 JJ=K{^f1S!SM5 4jZj)Sgi ,f*d*e*,yLLL=/T&GZ ]B5C- Գj322zPbBRRҞYTTz?PJk0sL5I9PҚ$(Si̓t#9PҚ$(Si̓tzA{:Hor(Si̓tzA{:I=TZ=7zA{:I=TZ=e*yОқ\||D=TZ=e*yОNRρ2e*yОNRρ26&C|x΂/hsEO  F,O,('8Y~r3qϼ;M|l'@m̑m x`;L67N 7ۘ#ۈ%39 wN`>p ng\Nc_%@7/x,`3Y ʗP~B|wrܣͣ؃9%@} ̑׼/~=Ncۏ ʃ9Fc7N 7 \W^.6>o%@,I`;'@} mucs39 pl%__ 9˃|p  q;~!"} `X.h' =f 8;!(`{E\;Zr gA9&@och{&?N~aB'l/&@> oM6Om\3N%@9 }  7&@||iHhwx bo !& H9~?,?,N(/(_D:~vΘ%@}|'~807 W}  7&@, p|%@, p|%@, p|%__^_Nc p|`;-(' 8 }`rU j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_arrow_down_4982.png000066400000000000000000000005731362177441300262600ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe< IDAT8˥?KQi H*.EA!XX vc!v{;,ӻ\}Ï7Ibc0u2B^h gw`4+@jɥ s5"H hPǁh"H.EBFݜ& 5EɃZn+;l8r9~c$ h>*k>hLLjƳRwUd:<$hݸIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_arrow_up_4999.png000066400000000000000000000005641362177441300257450ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥?/Qϲhg5j%'F,[ LH 3(vw=fcoN{O~9VM4Q7ܿ)v/WQ=&bpSO ^'&^:\˨6eND!& 9꒣_|?\ srx,g*,(F#d[OaAA*P p1O+C$`)*w`A#0$ *?b&NRIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_asterisk_yellow_5001.png000066400000000000000000000013471362177441300272760ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<yIDAT8˥KTaƟa-j9ijD ETQ@1 I&V);D-sD"̏QgRk9w5Z<9/Hb! ʼp,Ͼ3N?j5G+C.f%L*o3M`)8D{F[l9Z͹U}sud43bQ[RoIvr7ޤ q5c\|z*a$][ ,[RI"l12x'|FjaY/r A*f?LE ELwChP_ c5Pp)9fA7s 9/L~>7dS!{/V?@l*ydLކ ]Gp*{" 3tRI}/7K VtV.ۥPc >o"4TSH{Z6,<9 F}pL*f<҆-B]+,I+.~'sl#!lZuZѺl+wx !BNJH/:ePm%𴸗 L:%IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_cross_5233.png000066400000000000000000000012171362177441300252120ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<!IDAT8˕NQɉϠVȑB[( &^M6M| Dްҙv:官J-%Nd LB>w_3:*WrlNC/-좕B'{ u_a46ҽbߡE%D47;ٻƩ;8ˣ}>6[ӕS@*Z Qk>~͵hB\9uxZvYb J Cيٽ?BYvn&kft$,d9Zap\^ Y7 QJF 9=Q4 ؜Io SBpsI) Fv(@yՎވc\@ %% Z2h'@d(<|áaJuM@O⤁LGjd!X8Af 5J i K->w62ƾWH}:mP]XB0QX=ib_g=!Ftt…clrIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_page_add_5548.png000066400000000000000000000013431362177441300256160ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<uIDATMU=ߙ;cYA!Y؟H*FEnZ)U-KTrYbj?DVN){ʅt% }6q2=V}.vDžw}z᷶/ bPfvf`džY:LI@A JD(JΜ#w o^7=  BFh}RBdq\ֆX=)jCL5QB ZBP!AD6&:@ч,!dLP Z]ABP$)H ~bԜ/G&FdHВ:}soUkZq7+qj^a(d4'?/g:x#m;+ǽgvf}7->hgߞ1+qkKENݯ_4K'S5 XcZ*JJ#~0[@jӲ^~18--&;T߶řlw^~p=,+2tD] "(669]f߅I@A JD(JwɆ^߹V7=  BFh}RBd4jcS1Xe }O$hɍ#N}ྦྷ3K?}bB8L A##=}-`xU6o_ qu]ђ>郖[6༮ akrԋoZ%Wλ1G_4\::]*,/_w4E+;}V IL}oG)g$[?/`/} -%nQP`IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_page_white_get_5593.png000066400000000000000000000010041362177441300270370ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕MKQM!Z M [.PladdFqѢ@ ms5?0ssaka`0%0Xl4f# %Hj ۔J% iYJ :$$ Z $ORX,"A@D"Y# (P.f{ZjD!`m7;JdA7j UB jfB z.υ$ĿT C] (@Np>]P8c6 #֍+ޥ/>?>/'nqgջ7lQ+LGՂAsXzߋkf껰Ȇij}/bg<7G!Z#ɠlrs`%Co4tQЛ1IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_page_white_put_5609.png000066400000000000000000000010131362177441300270660ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˕KKaE"!hQAIe1- J \ jBA Rh-\HhA(::ۜ309g822P h0,0@ YU8.U**rv%@C\.eki$ID"Qx$tI@ Ţx',RѨ(0 #JZ 2 bXKKՙBL}KqP(YL*y$jfB\HB-*vױ}o9 ӭBT%h (L<ѧǨvdx:ٿ {Њ7'\;TS;s`ۄ#\0?vaV2+:.ŷw - ZeR:ؕߓֱ+(\ҴphIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_pencil_5631.png000066400000000000000000000007021362177441300253330ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<TIDAT8˭?Hq][ p@\hZr0+ʐܲHlHH*$:2,S *A = _B\x7ÖZc<oozڈBЊ" 9!B!>gvڀ,;SRNXjD|D62 ( 8`~vPOoCvs 8 ]|aɄ[ z/Nİ*tuXİ*QT.;kDbՀ̱ <ݏy#~=庠q]}fĝZtlT3R%~>#w -;06m7wU\&IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/iconfinder_stop_5785.png000066400000000000000000000012741362177441300250650ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<NIDAT8˥=OTAݻ zAPITJ :c?5FH@J~VaȢ| DCv3sgbC-'99'9DO).zF]%Vf6Co ? 0 c J)H) g'ι-x [@(űGqn,'4j'kɱyY rdB$sޭ8؟IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_arrow_redo_4989.png000066400000000000000000000011611362177441300245210ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8˥OHQ7Zcf9UԢت fdPlQ RȕnE"à6e-r#apd6Yc|lDe{yDQؿ<>3;uDٖ47KgAg)g$ujUO 2kp s܍ { t?<(7YՔLX]`$7id? 6 \O_ٿ눊>IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_cog_5175.png000066400000000000000000000010001362177441300231020ustar00rootroot00000000000000PNG  IHDR7gAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT(UQMKa^ tT2 .EQС:EeQPR麖QRq%!O^wm!c333 To^%ۚ\Y|gz),{ b,پ7g| fʨ'TǂLGUM W0FDNC \`BASJk4$0SrI ZHd (cO:/B&69I#M:X61đ|hK5AJTȐ9Zf) 9+<OQ  **4KrHk?WDj L>c!i9BL%^0n9ֈs>i ֓i붻k>Gj ]IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_cut_red_5249.png000066400000000000000000000012121362177441300237660ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?.=йnBI03vEf3Db'r"'Lb#U^.8Y&6Mǻai n^S)S6,9:yV h__sSb}>wՇ)&e@pƖsWHZЭ7^kZYMt lMskHNSl3t=7? jIS'24`3n'qXMdayVgHg4఍ћzAR3/8/]|5ԿB~7/VZ;iot꡻^hdrѧ6.҂' q(`5`$q[灊VkojآVN2Sڙc }lj]܌ lܽnrcпe}N`Lɠ+?TrIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_disk_5276.png000066400000000000000000000011541362177441300233000ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT=]Uе;C`DD Ve h*B"6VV"c vba"NB ޙ{~jo|x&,K*o=]߻_v<}tu.R[Ҝ9Xsy|o~ |u_Bk6Yf7bQڴ^Й<T::֊Ǧ7Uݕuwٞ ,6}4'kٞF%*T⏿NHZ'}&NGۯ_鋾ﴌ*UD:tHُZkƦ/F%8A#хJpFQ$Q5:H0' QEDDK$e]t4IJDUS*!BxKOƈ5Hup9Q pҫj'ɬ4nx93W3j7˜oO$*yHI$H=m?!|nmIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_folder_5362.png000066400000000000000000000010311362177441300236070ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œA[EtQg7wALCWA0P017p2=:A3cb2'pܪ$Vme@ ݬ2OTO1W/`z8% ;O;9P#9B}^nO;Ǫo~ d~E dpɳ__jMMIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_paste_plain_5629.png000066400000000000000000000011351362177441300246460ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8ˍ?hSQ//&%h?IDH ., .\B7ApN.:H:b'KEjim޻|ɋi{n$ s私O FT*V:z;z-/A@Xz晱R$a]$1sq HXidgAĖP8! \n#<𴃧 '">`bPw |?2MZrw 3R$$ 8m6K}!E(8nt:HY_ !ׁ9[PV^ Pp xR1.; ,;ہ!(>$D;x&)BSDVgwxvu \yv*)DQ4q6;;33;0Ezdk7IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_resultset_last_5687.png000066400000000000000000000010141362177441300254240ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œkQ7; *,E/A^TI%XX,,?H6ɨA QL&q>X&_q81)%v]j1W٫G ;E)}z>wOu{G%lj٘=*l__v$>lIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_resultset_next_5688.png000066400000000000000000000006131362177441300254440ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8c?%4~nv xf%e@gמx}Ë Sl@ނGWK_%<\dD`Z~'UKX#꺝([m/ػ@J:t6%y]Dr4JDo]x濨d%$1ED*뿀IENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_shape_square_5750.png000066400000000000000000000005411362177441300250220ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT8œ1N@DNJ13$$\r;]^kl4Ngv'{wR(Q˴3x~zDĀ& |^hTtDI%¥V9X$ p1lĠR47/u6!ͣAZtl3֠*kFrKzW9 P`XquguB`~#NH>k$X =o7oWem,OIENDB`BambooTracker-0.3.5/BambooTracker/res/icon/if_table_edit_5800.png000066400000000000000000000013501362177441300244310ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<zIDAT8˥YHQeƲ1k2 6+mq Z^" "|Z Az A_ ,*u&\(4BetԱt(AQtss 5lkGOk UR @( 4B["F*HnPZ K7>nL0Jt_A(W \NW)w'2i@܇Uo1+R*|wdmKGmdG:7CoHn`D^1{Rs61i5m.T(-]?ei0#b#sjk8]oԄ ؖBj^9H 6􌉁V1wo6&PV`P&eP b/<0W#E,dm55IO2ʼnf3l)8WoPq-ӠiWG'Dz^*,f*o̫S" 2kt/Fߴ]RInb{ưL2.aRM*(%'HGM>Խ) CommentEditDialog Module Comment Commentaire du module ConfigurationDialog Configuration Configuration General Général Fill 00 to effect value Autoset instrument Move cursor by horizontal scroll Use SCCI Emulation core Cœur d'émulation API MIDI MIDI MIDI input Entrée MIDI The level setting for each part is valid when the mixer in the module properties is not checked. Appearance Font and size Order list rows Pattern editor rows Pattern editor header Order list header Colors Item Color Pattern editor Default step text Default step background Highlighted step 1 background Highlighted step 2 background Current step text Current step background Current editing step background Current cell background Current playing step background Selection background Hovered cell background Default step number Highlighted step 1 number Highlighted step 2 number Note text Instrument text Volume text Effect text Error text Header text Header background Mask Border Mute Unmute Background Order list Default row text Default row background Current row text Current row background Current editing row background Current playing row background Row number Instrument list Default text Selected background Hovered background Selected hovered background Oscilloscope Foreground Save Load Formats Formats Keys Touches Note entry layout Agencement pour la saisie de notes Custom Personnalisé Low Bas C Do C# Do# D D# Ré# E Mi F Fa F# Fa# G Sol G# Sol# A La A# La# B Si High Haut Special keys Touches spéciales Key off Relâchement de touche Octave up Octave supérieure Octave down Octave inférieure Echo buffer Tampon d'écho Edit settings Paramètres d'édition Page jump length Longueur du saut de page General settings Paramètres généraux Warp cursor J'ai rien trouvé de mieux. Déplacer le curseur de manière cyclique Warp across orders J'ai rien trouvé de mieux. Incertitude à propos de la traduction adéquate pour "order" Se déplacer de manière cyclique le long des ordres Show row numbers in hex Numéroter les rangées en hexa Preview previous/next orders Incertitude à propos de la traduction adéquate pour "order" Prévisualiser les ordres précédent/suivant Backup modules Sauvegarder les modules Don't select on double click Ne pas sélectionner avec un double clic Reverse FM volume order Inverser la valeur du volume FM Move cursor to right Déplacer le curseur vers la droite Retrieve channel state Récupérer l'état des canaux Enable translation Activer la traduction Show FM detune as signed Afficher le désaccordage FM comme valeur signée Show wave visual Afficher une visualisation de l'onde Description: Description : Sound Son Sample rate Taux d'échantillonnage Buffer length Taille de la mémoire tampon 1ms 1ms Device Périphérique Use SCCI (beta) Utiliser SCCI (bêta) FM envelope text Texte d'enveloppe FM Add Ajouter Edit Éditer Remove Supprimer Mixer Mélangeur Part Partie Reset Réinitialiser Virtual port Port virtuel Master Maître The change of emulator will be effective after restarting the program. Le changement d'émulateur sera pris en compte au redémarrage du logiciel. Warp the cursor around the edges of the pattern editor. J'ai rien trouvé de mieux. Le curseur se déplace de manière cyclique à proximité des bords de l'éditeur de motifs. Move to previous or next order when reaching top or bottom in the pattern editor. Incertitude à propos de la traduction adéquate pour "order" Se positionner sur l'ordre précédent ou suivant lorsque le sommet ou le bas de l'éditeur de motifs est atteint. Display order numbers and the order count on the status bar in hexadecimal. Incertitude à propos de la traduction adéquate pour "order" Afficher le numéro d'ordre et le nombre d'ordres en hexadécimal dans la barre d'état. Display row numbers and the playback position on the status bar in hexadecimal. Preview previous and next orders in the pattern editor. Incertitude à propos de la traduction adéquate pour "order" Prévisualiser les ordres précédent et suivant dans l'éditeur de motifs. Create a backup copy of the existing file when saving a module. Créer une copie de sauvegarde du fichier existant lors de la sauvegarde d'un module. Don't select the whole track when double-clicking in the pattern editor. Ne pas sélectionner la piste entière lors d'un double-clic dans l'éditeur de motif. Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Inverser la valeur du volume FM pour que 00 soit le plus bas dans l'éditeur de motif. Move the cursor to right after entering effects in the pattern editor. Déplacer le curseur vers la droite après être entré dans les effets dans l'éditeur de motif. Reconstruct the current channel's state from previous orders upon playing. Reconstruire l'état du canal actuel à partir des ordres précédents durant la lecture. Translate to your language from the next launch. See readme to check supported languages. Traduire en votre langue au prochain démarrage. Lire le fichier readme pour connaître la liste des langues gérées. Display FM detune values as signed numbers in the FM envelope editor. Afficher les valeurs de désaccordage FM comme nombres signés dans l'éditeur d'enveloppe FM. Enable an oscilloscope which displays a waveform of the sound output. Activer un oscilloscope qui affiche la forme d'onde du signal de sortie. Fill 00 to effect value column upon entering effect id. Set current instrument upon entering note. Move the cursor position by cell with horizontal scroll bar in the order list and the pattern editor. Description: %1 Set %1 Description: Description : Open color scheme ini file (*.ini) Error Erreur An unknown error occurred while loading the color scheme. Save color scheme Failed to save the color scheme. EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpège, x : 2nde note (0-F), y : 3ème note (0-F) Portamento up, xx: depth (00-FF) Portamento haut, xx : profondeur (00-FF) Portamento down, xx: depth (00-FF) Portamento bas, xx : profondeur (00-FF) Tone portamento, xx: depth (00-FF) Portamento de tonalité, xx : profondeur (00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x : période (0-F), y : profondeur (0-F) Tremolo, x: period (0-F), y: depth (0-F) Trémolo, x : période (0-F), y : profondeur (0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre Volume slide, x: up (0-F), y: down (0-F) Glissé de volume, x : haut (0-F), y : bas (0-F) Jump to beginning of order xx Aller au début de la commande xx End of song Fin du morceau Jump to step xx of next order Aller au pas xx de la prochaine commande Change speed (xx: 00-1F), change tempo (xx: 20-FF) Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) Note delay, xx: count (00-FF) Délai de note, xx : compte (00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) Hardware envelope period 1, xx: high byte (00-FF) Hardware envelope period 2, xx: low byte (00-FF) Set groove xx Paramétrer le groove xx Detune, xx: pitch (00-FF) Désaccordage, xx : tonalité (00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) Note slide down, x: count (0-F), y: seminote (0-F) Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) Note cut, xx: count (01-FF) Coupure de note, xx : compte (01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise Master volume, xx: volume (00-3F) Volume maître, xx : volume (00-3F) Noise pitch, xx: pitch (00-1F) Register address bank 0, xx: address (00-6B) Register address bank 1, xx: address (00-6B) Register value set, xx: value (00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) Brightness, xx: relative value (01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) FB control, xx: feedback value (00-07) ML control, x: operator (1-4), y: multiple (0-F) Volume delay, x: count (1-F), yy: volume (00-FF) Délai de volume, x : compte (1-F), yy : volume (00-FF) RR control, x: operator (1-4), y: release rate (0-F) TL control, x: operator (1-4), yy: total level (00-7F) Invalid effect Effet invalide EffectListDialog Effect list Liste d'effets Effect Effet Track type Type de piste Description Description Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) Arpège, x : 2nde note (0-F), y : 3ème note (0-F) Portamento up, xx: depth (00-FF) Portamento haut, xx : profondeur (00-FF) Portamento down, xx: depth (00-FF) Portamento bas, xx : profondeur (00-FF) Tone portamento, xx: depth (00-FF) Portamento de tonalité, xx : profondeur (00-FF) Vibrato, x: period (0-F), y: depth (0-F) Vibrato, x : période (0-F), y : profondeur (0-F) Tremolo, x: period (0-F), y: depth (0-F) Trémolo, x : période (0-F), y : profondeur (0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre Volume slide, x: up (0-F), y: down (0-F) Glissé de volume, x : haut (0-F), y : bas (0-F) Jump to begginning of order xx Aller au début de la commande xx Jump to beginning of order xx Aller au début de la commande xx End of song Fin du morceau Jump to step xx of next order Aller au pas xx de la prochaine commande Change speed (xx: 00-1F), change tempo (xx: 20-FF) Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) Note delay, xx: count (00-FF) Délai de note, xx : compte (00-FF) Set groove xx Paramétrer le groove xx Detune, xx: pitch (00-FF) Désaccordage, xx : tonalité (00-FF) Note slide up, x: count (0-F), y: seminote (0-F) Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) Note slide down, x: count (0-F), y: seminote (0-F) Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) Note cut, xx: count (01-FF) Coupure de note, xx : compte (01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) Master volume, xx: volume (00-3F) Volume maître, xx : volume (00-3F) Volume delay, x: count (1-F), yy: volume (00-FF) Délai de volume, x : compte (1-F), yy : volume (00-FF) FMEnvelopeSetEditDialog Edit FM envelope set Éditer le format d'enveloppe FM Add Ajouter Set digit types in the order of appearence. Set digit types in the order of appearence: Définir les types de valeurs d'après l'ordre d'apparition : Remove Supprimer Number Numéro Type Type Skip FMOperatorTable Operator Opérateur SSGEG SSGEG Type Type Operator %1 Copy envelope Copier l'enveloppe Paste envelope Coller l'enveloppe Paste envelope From Coller l'enveloppe depuis Copy operator Copier l'opérateur Paste operator Coller l'opérateur GrooveSettingsDialog Groove Settings Paramètres du groove Remove Supprimer Add Ajouter Sequence Séquence Copy Fxx Copier Fxx Expand Étendre Generate Générer Shrink Réduire Pad Remplir Speed: Vitesse : Speed: %1 InstrumentEditorFMForm Envelope Enveloppe Users: Utilisateurs : LFO/Operator sequence Incertitude sur ce qu'est vraiment "operator sequence". À vérifier LFO/Séquence d'opérateurs LFO LFO Start count: Compteur initial : AM operators Opérateurs AM Operator 1 Opérateur 1 Operator 2 Opérateur 2 Operator 3 Opérateur 3 Operator 4 Opérateur 4 Envelope reset Réinitialisation d'enveloppe Reset envelope before key on Abrégé pour rentrer dans le cadre Réinitialiser l'env. en début de note Operator sequence Incertitude sur ce qu'est vraiment "operator sequence". À vérifier Séquence d'opérateurs Operator: Opérateur : Sequence Séquence FM 3ch FM 3canaux Arpeggio/Pitch Arpège / hauteur tonale Arpeggio Arpège Type: Type : Pitch Hauteur tonale Absolute Absolu Fix Fixe Freq All Fixed Fixe Relative Relatif Error Erreur Did not match the clipboard text format with %1. Impossible d'interpréter le texte formaté du presse-papiers avec %1. Copy envelope Copier l'enveloppe Paste envelope Coller l'enveloppe Paste envelope From Coller l'enveloppe depuis Copy LFO parameters Copier les paramètres du LFO Paste LFO parameters Coller les paramètres du LFO InstrumentEditorSSGForm Wave form Forme d'onde Users: Utilisateurs : Square mask À vérifier Masque carré Raw À vérifier Brut Tone/Mask ratio À vérifier Rapport tonalité/masque Hard frequency À vérifier... Fréquence dure Tone/Hard ratio À vérifier... Rapport tonalité/dur Waveform Tone/Noise Tonalité/bruit Envelope Enveloppe Hardware envelope frequency Tone/Env ratio Arpeggio Arpège Type: Type : Pitch Tonalité Sq Tri Saw InvSaw SMTri SMSaw SMInvSaw HEnv %1 Absolute Absolu Fixed Fixe Fix Fixe Relative Relatif InstrumentSelectionDialog Select instruments Sélectionner les instruments Search... Rechercher... KeyboardShortcutListDialog Keyboard shortcuts Raccourcis clavier qrc:/doc/shortcuts MainWindow BambooTracker BambooTracker Order List Incertitude à propos de la traduction adéquate pour "order" Liste d'ordres Module Settings Paramètres du module Copyright Droits d'auteur Author Auteur Module Title Titre du module Edit settings Paramètres d'édition Editable step Intervalle éditable Key repetition Répétition des touches Song Morceau # # Song Settings Paramètres du morceau Speed Vitesse Pattern Size Taille de motif Tempo Tempo Style Style Untitled Sans titre Groove Groove Instruments Instruments &File &Fichier &Export &Exporter &Recent Files Fichiers &récents &Edit &Édition &Select &Sélectionner Paste Specia&l Collage spécia&l Patter&n &Motif Son&g Mor&ceau &Tracker &Tracker &Pattern &Motif &Transpose &Transposer Pattern size Step Title Titre Songs &Module &Module Clean&up &Nettoyer &Instrument &Instrument &Help &Aide Main toolbar Barre d'outils principale Secondary toolbar Barre d'outils secondaire &New... &Nouveau... Ctrl+N Ctrl+N &Open... &Ouvrir... Ctrl+O Ctrl+O &Save &Sauvegarder Ctrl+S Ctrl+S Save &As... S&auvegarder sous... E&xit &Quitter &Undo Ann&uler Ctrl+Z Ctrl+Z &Redo &Rétablir Ctrl+Y Ctrl+Y Cu&t Cou&per Ctrl+X Ctrl+X &Copy &Copier Ctrl+C Ctrl+C &Paste Co&ller Ctrl+V Ctrl+V &Delete &Supprimer Del Suppr &All &Tout Ctrl+A Ctrl+A &None &Aucun Esc Échap E&xpand É&tendre S&hrink &Réduire &Decrease Note &Diminuer la note Ctrl+F1 Ctrl+F1 &Increase Note &Augmenter la note Ctrl+F2 Ctrl+F2 D&ecrease Octave Diminu&er l'octave Ctrl+F3 Ctrl+F3 I&ncrease Octave Augme&nter l'octave Ctrl+F4 Ctrl+F4 &Insert Order Incertitude à propos de la traduction adéquate pour "order" &Insérer un ordre &Remove Order Incertitude à propos de la traduction adéquate pour "order" Supp&rimer l'ordre &Module Properties... Propriétés du &module... Ctrl+P Ctrl+P &New Instrument &Nouvel instrument &Remove Instrument Supp&rimer l'instrument &Clone Instrument &Cloner l'instrument &Deep Clone Instrument Cloner l'instrument en profon&deur &Load From File... &Charger depuis un fichier... &Save To File... &Sauvegarder vers un fichier... &Edit... &Éditer... Ctrl+I Ctrl+I &Play &Jouer Play P&attern Jouer le &motif F6 F6 Play &From Start Jouer &depuis le début F5 F5 Play From C&ursor Jouer depuis le c&urseur F7 F7 &Stop &Arrêter F8 F8 &Edit Mode Mode &édition Space Espace To&ggle Track &Basculer la piste Alt+F9 Alt+F9 S&olo Track Mettre la piste en s&olo Alt+F10 Alt+F10 &Kill Sound &Tuer le son F12 F12 &About... À &propos... Fo&llow Mode Mode &suivi ScrollLock ArrêtDéfil &Groove Settings... Paramètres du &groove... &Configuration... &Configuration... &Duplicate Order Incertitude à propos de la traduction adéquate pour "order" &Dupliquer l'ordre Ctrl+D Ctrl+D Move Order &Up Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le ha&ut Move Order Do&wn Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le &bas &Clone Patterns &Cloner les motifs Alt+D Alt+D Clone &Order Incertitude à propos de la traduction adéquate pour "order" Cloner l'&ordre &Comments... &Commentaires... &Interpolate &Interpoler Ctrl+G Ctrl+G &Reverse Inve&rser Ctrl+R Ctrl+R R&eplace Instrument R&emplacer l'instrument Alt+S Alt+S &Row &Rangée &Column &Colonne &Order Incertitude à propos de la traduction adéquate pour "order" &Ordre Remove Unused &Instruments Supprimer les &instruments inutilisés Remove Unused &Patterns Sup&primer les motifs inutilisés &WAV... &WAV... &VGM... &VGM... &Mix &Mélange Ctrl+M Ctrl+M &Overwrite É&craser &Import From Bank File... &Importer depuis un fichier de banque... &S98... &S98... &Clear &Effacer &Effect List... Liste d'&effets... Effect List Liste d'effets F1 F1 &Shortcuts... &Raccourcis... E&xport To Bank File... E&xpand Effect Column Alt+L S&hrink Effect Column Alt+K Remove &Duplicate Instruments Re&name Instrument Octave Octave Octave: %1 Octave : %1 Save changes to %1? Sauvegarder les modifications dans %1 ? Error Erreur Instrument %1 Instrument %1 Open instrument Ouvrir l'instrument Failed to load instrument. Le chargement de l'instrument a échoué. Save instrument Sauvegarder l'instrument Open bank Ouvrir la banque Select instruments to load: Sélectionnez les instruments à charger : No instrument Pas d'instrument Standard Standard Step highlight 1st 1er intervalle en surbrillance 2nd 2nd BambooTracker instrument (*.bti) Instrument BambooTracker (*.bti) DefleMask preset (*.dmp) Préréglage DefleMask (*.dmp) TFM Music Maker instrument (*.tfi) Instrument TFM Music Maker (*.tfi) VGM Music Maker instrument (*.vgi) Instrument VGM Music Maker (*.vgi) WOPN instrument (*.opni) Instrument WOPN (*.opni) Gens KMod dump (*.y12) Décharge Gens KMod (*.y12) MVSTracker instrument (*.ins) Instrument MVSTracker (*.ins) BambooTracker instrument file (*.bti) Fichier instrument BambooTracker (*.bti) BambooTracker bank (*.btb) WOPN bank (*.wopn) Banque WOPN (*.wopn) Select instruments to save: Save bank - Custom PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 NEC PC-8801mkIISR FM3ch expanded FM3canaux étendu Could not initialize MIDI input. module s98 vgm wav bank instrument Failed to load the %1. Failed to export to %1. Failed to save the %1. Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. Could not load the %1. It may be corrupted. Instrument: %1 BambooTracker module (*.btm) WAV signed 16-bit PCM (*.wav) VGM file (*.vgm) S98 file (*.s98) Do you want to remove all duplicate instruments? &Add &Ajouter &Remove Supp&rimer Edit &name Éditer le &nom &Clone &Cloner &Deep clone Cloner en &profondeur &Load from file... &Charger depuis un fichier... &Save to file... &Sauvegarder dans le fichier... &Import from bank file... &Importer depuis un fichier de banque... <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018, 2019 Rerrah</b><br><hr>Libraries:<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- nowide by (C) Artyom Beilis (BSL v1.0)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI (SCCI License)<br>- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018, 2019 Rerrah</b><br><hr>Bibliothèques:<br>- libOPNMIDI par (C) Vitaly Novichkov (en partie sous licence MIT)<br>- MAME (licence MAME)<br>- nowide par (C) Artyom Beilis (BSL v1.0)<br>- Nuked OPN-MOD par (C) Alexey Khokholov (Nuke.YKT)<br>et (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtMidi par (C) Gary P. Scavone (licence RtMidi)<br>- SCCI (licence SCCI)<br>- ensemble d'icône Silk 1.3 par (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ ou LGPL v3)<br>- VGMPlay par (C) Valley Bell (GPL v2)<br><br>Lire également le changelog qui contient la liste des contributeurs. Instrument: Instrument : Do you want to change song properties? Voulez-vous modifier les propriétés du morceau ? Change to jam mode Passage en mode jeu Change to edit mode Passage en mode édition About À propos Failed to backup module. La sauvegarde du module a échoué. The number of instruments has reached the upper limit. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018-2020 Rerrah</b><br><hr>Libraries:<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI (SCCI License)<br>- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. Save module Sauvegarder le module BambooTracker module file (*.btm) Fichier de module BambooTracker (*.btm) Open module Ouvrir le module Do you want to remove all unused instruments? Voulez-vous supprimer tous les instruments inutilisés ? Do you want to remove all unused patterns? Voulez-vous supprimer tous les motifs inutilisés ? Export to wav Exporter en wav Export to WAV Exporter en WAV Cancel Annuler Failed to export to wav file. L'exportation en fichier wav a échoué. Export to vgm Exporter en vgm Export to VGM Exporter en VGM Failed to export to vgm file. L'exportation en fichier vgm a échoué. Export to s98 Exporter en s98 Export to S98 Exporter en S98 Failed to export to s98 file. L'exportation en fichier s98 a échoué. Warning %1 If you execute this command, the command history is reset. Could not open the audio stream. Please change the sound settings in Configuration. ModulePropertiesDialog Module properties Propriétés du module Tick frequency Fréquence d'horloge 60Hz (NTSC) 60Hz (NTSC) 50Hz (PAL) 50Hz (PAL) Custom Autre Mixer Mélangeur PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR Custom mixer FM FM SSG SSG Set Song control Contrôle du morceau Remove Supprimer Insert song Insérer un morceau Title: Titre : Song type: Type de morceau : Insert Insérer Untitled Sans titre Number Numéro Title Titre Song type Type de morceau Standard Standard FM3ch expanded FM3canaux étendu OrderListPanel &Insert Order Incertitude à propos de la traduction adéquate pour "order" &Insérer un ordre &Remove Order Incertitude à propos de la traduction adéquate pour "order" Supp&rimer l'ordre &Duplicate Order Incertitude à propos de la traduction adéquate pour "order" &Dupliquer l'ordre &Clone Patterns &Cloner les motifs Clone &Order Incertitude à propos de la traduction adéquate pour "order" Cloner l'&ordre Move Order &Up Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le ha&ut Move Order Do&wn Incertitude à propos de la traduction adéquate pour "order" Déplacer l'ordre vers le &bas Cop&y Cop&ier &Paste Co&ller PatternEditorPanel Invalid effect Effet invalide 00xy - Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) 00xy - Arpège, x : 2nde note (0-F), y : 3ème note (0-F) 01xx - Portamento up, xx: depth (00-FF) 01xx - Portamento haut, xx : profondeur (00-FF) 02xx - Portamento down, xx: depth (00-FF) 02xx - Portamento bas, xx : profondeur (00-FF) 03xx - Tone portamento, xx: depth (00-FF) 03xx - Portamento de tonalité, xx : profondeur (00-FF) 04xy - Vibrato, x: period (0-F), y: depth (0-F) 04xx - Vibrato, x : période (0-F), y : profondeur (0-F) 07xx - Tremolo, x: period (0-F), y: depth (0-F) 07xx - Trémolo, x : période (0-F), y : profondeur (0-F) 08xx - Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center 08xx - Pan, xx : 00 = pas de son, 01 = droite, 02 = gauche, 03 = centre 0Axy - Volume slide, x: up (0-F), y: down (0-F) 0Axy - Glissé de volume, x : haut (0-F), y : bas (0-F) 0Bxx - Jump to begginning of order xx 0Bxx - Aller au début de la commande xx 0Cxx - End of song 0Cxx - Fin du morceau 0Dxx - Jump to step xx of next order 0Dxx - Aller au pas xx de la prochaine commande 0Oxx - Set groove xx 0Oxx - Paramétrer le groove xx 0Pxx - Detune, xx: pitch (00-FF) 0Pxx - Désaccordage, xx : toalité (00-FF) 0Qxy - Note slide up, x: count (0-F), y: seminote (0-F) 0Qxy - Note glissée vers le haut, x : compte (0-F), y : demi-note (0-F) 0Rxy - Note slide down, x: count (0-F), y: seminote (0-F) 0Rxy - Note glissée vers le bas, x : compte (0-F), y : demi-note (0-F) 0Txy - Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) 0Txy - Délai de transposition, x : compte (1-7 : haut, 9-F : bas), y : demi-note (0-F) 0Fxx - Change speed (xx: 00-1F), change tempo (xx: 20-FF) 0Fxx - Changement de vitesse (xx : 00-1F), changement de tempo (xx : 20-FF) 0Gxx - Note delay, xx: count (00-FF) 0Gxx - Délai de note, xx : compte (00-FF) 0Sxx - Note cut, xx: count (01-FF) 0Sxx - Coupure de note, xx : compte (01-FF) 0Vxx - Master volume, xx: volume (00-3F) 0Vxx - Volume maître, xx : volume (00-3F) Mxyy - Volume delay, x: count (1-F), yy: volume (00-FF) Mxyy - Délai de volume , x : compte (1-F), yy : volume (00-FF) &Undo Ann&uler &Redo &Rétablir &Copy &Copier Cu&t Cou&per &Paste C&oller Paste Specia&l Collage spécia&l &Mix &Mélange &Overwrite &Écraser &Erase &Effacer Select &All Sélectionner &tout Patter&n &Motif &Interpolate &Interpoler &Reverse Inve&rser R&eplace Instrument R&emplacer l'instrument E&xpand É&tendre S&hrink Réd&uire &Transpose &Transposer &Decrease Note &Diminuer la note &Increase Note Aug&menter la note D&ecrease Octave Diminu&er l'octave I&ncrease Octave A&ugmenter l'octave To&ggle Track &Basculer la piste &Solo Track Piste &solo Expand E&ffect Column Shrin&k Effect Column &Unmute All Tracks Rendre a&udibles toutes les pistes QObject Error Erreur An unknown error occured. %1 Une erreur inconnue s'est produite. %1 An unknown error occured. Une erreur inconnue s'est produite. An unknown error occurred. An unknown error occurred. %1 S98ExportSettingsDialog S98 export settings Paramètres d'exportation S98 Tag Étiquette Title Titre Artist Artiste Game Jeu Year Année Genre Genre Comment Commentaire Copyright Droits d'auteur S98by S98par System Système NEC PC-9801 NEC PC-9801 Target Cible FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN SSG SSG OPN internal Interne OPN AY-3-8910 PSG PSG AY-3-8910 YM2149 PSG PSG YM2149 Support Support FM Channels Canaux FM Yes Oui Drums Rhythm Rythme ADPCM ADPCM Resolution Résolution No Non ToneNoiseMacroEditor Tone Noise VgmExportSettingsDialog VGM export settings Paramètres d'exportation VGM GD3 tag Étiquette GD3 Game Jeu Name Nom English Anglais Release date Date de sortie Japanese Japonais System Système NEC PC-9801 NEC PC-9801 Track Piste Title Titre Author Auteur VGM file Fichier VGM Creator Créateur Notes Notes Target Cible FM FM YM2608 OPNA YM2608 OPNA YM2612 OPN2 YM2612 OPN2 YM2203 OPN YM2203 OPN SSG SSG OPN internal Interne OPN AY-3-8910 PSG PSG AY-3-8910 YM2149 PSG PSG YM2149 Support Support FM Channels Canaux FM Yes Oui Drums Rhythm Rythme ADPCM ADPCM No Non VisualizedInstrumentMacroEditor Size: 1 Taille : 1 Size: %1 Taille : %1 Loop Boucle Loop %1 Boucle %1 Fixed Fixe Release Relâche Fix Fixe Absolute Absolu Relative Relatif WaveExportSettingsDialog WAV export settings Paramètres d'exportation WAV Loop Boucle Loop: Boucle : Sample rate Taux d'échantillonnage BambooTracker-0.3.5/BambooTracker/res/lang/bamboo_tracker_ja.ts000066400000000000000000004230521362177441300244710ustar00rootroot00000000000000 CommentEditDialog Module Comment モジュールコメント ConfigurationDialog Configuration 設定 General 全体設定 Move cursor by horizontal scroll 横スクロールでカーソルを移動 Emulation core エミュレーション API MIDI MIDI MIDI input MIDI In The level setting for each part is valid when the mixer in the module properties is not checked. 各パートの音量レベル設定はモジュールプロパティのミキサーがチェックされていない時のみ有効になります。 Appearance 外観 Font and size フォントとサイズ Order list rows オーダーリスト行 Pattern editor rows パターンエディター行 Pattern editor header パターンエディターヘッダー Order list header オーダーリストヘッダー Colors カラースキーム Item アイテム Color Pattern editor パターンエディター Default step text デフォルトステップテキスト Default step background デフォルトステップ背景 Highlighted step 1 background ハイライトステップ1背景 Highlighted step 2 background ハイライトステップ2背景 Current step text 現在のステップテキスト Current step background 現在のステップ Current editing step background 現在編集中のステップ背景 Current cell background 現在のセル背景 Current playing step background 現在再生中のステップ背景 Selection background 選択領域背景 Hovered cell background マウスカーソルのセル背景 Default step number デフォルトステップ番号 Highlighted step 1 number ハイライトステップ1番号 Highlighted step 2 number ハイライトステップ2番号 Note text 音符テキスト Instrument text インストゥルメントテキスト Volume text 音量テキスト Effect text エフェクトテキスト Error text エラーテキスト Header text ヘッダーテキスト Header background ヘッダー背景 Mask マスク Border 境界線 Mute ミュート状態 Unmute ミュート解除状態 Background 背景 Order list オーダーリスト Default row text デフォルト行テキスト Default row background デフォルト行背景 Current row text 現在の行テキスト Current row background 現在の行背景 Current editing row background 現在編集中の行背景 Current playing row background 現在再生中の行背景 Row number 行番号 Instrument list インストゥルメントリスト Default text デフォルトテキスト Selected background 選択背景 Hovered background マウスカーソル背景 Selected hovered background マウスカーソル選択背景 Oscilloscope オシロスコープ Foreground 前景 Save 保存 Load 読み込み Formats フォーマット Keys キーボード Note entry layout 音符入力レイアウト Custom カスタム Low 低音 C C C# C# D D D# D# E E F F F# F# G G G# G# A A A# A# B B High 高音 Special keys 特殊キー Key off キーオフ Octave up オクターブアップ Octave down オクターブダウン Echo buffer エコーバッファ Edit settings エディット設定 Page jump length Page jump length: ページジャンプ長 General settings 総合設定 Warp cursor カーソルをワープ Warp across orders オーダー間をワープ Show row numbers in hex 行番号を16進数で表示 Preview previous/next orders 前後のオーダーをプレビュー Backup modules モジュールのバックアップ Don't select on double click ダブルクリックで選択しない Reverse FM volume order FMの音量の逆順にする Move cursor to right カーソルを右に移動 Retrieve channel state チャンネル状態を復元 Enable translation 翻訳を有効 Show FM detune as signed FMデチューンを符号付きで表記 Show wave visual オシロスコープ表示 Fill 00 to effect value エフェクト値に00を自動で設定 Autoset instrument インストゥルメント自動入力 Description: 説明: Sound サウンド Sample rate サンプルレート Buffer length バッファ長 1ms Device デバイス FM envelope text FMエンベロープテキスト Add 追加 Edit 編集 Remove 削除 Use SCCI SCCI使用 Mixer ミキサー Part パート Reset リセット Virtual port 仮想ポート Master マスター The change of emulator will be effective after restarting the program. エミュレータのの変更は次回起動以降に反映されます。 Warp the cursor around the edges of the pattern editor. パターンの両端をワープします。 Move to previous or next order when reaching top or bottom in the pattern editor. パターンの上下の端から前後のパターンへ移動します。 Display row numbers and the playback position on the status bar in hexadecimal. オーダー番号とステップ番号を16進数で表示します。 Preview previous and next orders in the pattern editor. パターンエディターで前後のオーダーをプレビューします。 Create a backup copy of the existing file when saving a module. モジュール保存時に既存のデータをコピーしてバックアップを作成します。 Don't select the whole track when double-clicking in the pattern editor. パターンエディターでのダブルクリック時にトラック全体を選択しないようにします。 Reverse the order of FM volume so that 00 is the quietest in the pattern editor. Reverse the order of FM volume so that 00 is the quietest in the pattern editor パターンエディターでFMの音量を00が最小になるよう順序を変更します。 Move the cursor to right after entering effects in the pattern editor. パターンエディターでエフェクト入力後にカーソルを右の列へ移動します。 Reconstruct the current channel's state from previous orders upon playing. 再生時に以前のオーダーから現在のチャンネルの状態を再構築します。 Translate to your language from the next launch. See readme to check supported languages. テキストが次回の起動時からあなたの言語に翻訳されます。サポートされている言語についてはreadmeを参照してください。 Display FM detune values as signed numbers in the FM envelope editor. FMエンベロープエディタでFMのデチューン値を符号付きの値として表示します。 Enable an oscilloscope which displays a waveform of the sound output. 出力波形を表示するオシロスコープを有効にします。 Fill 00 to effect value column upon entering effect id. エフェクトIDを入力時にエフェクト値を自動で00に設定します。 Set current instrument upon entering note. 音符入力時に現在のインストゥルメントを自動で入力します。 Move the cursor position by cell with horizontal scroll bar in the order list and the pattern editor. オーダーリストとパターンエディターにおいて横スクロールバーでカーソルをセル単位で移動させます。 Description: %1 説明: %1 Set %1 セット%1 Open color scheme カラースキームの読み込み ini file (*.ini) Error エラー An unknown error occurred while loading the color scheme. カラースキームの読み込み中に不明なエラーが発生しました。 Save color scheme カラースキームの保存 Failed to save the color scheme. カラースキームの保存に失敗しました。 EffectDescription Arpeggio, x: 2nd note (0-F), y: 3rd note (0-F) アルペジオ, x: 第2音(0-F), y: 第3音(0-F) Portamento up, xx: depth (00-FF) ポルタメント・アップ, xx: デプス(00-FF) Portamento down, xx: depth (00-FF) ポルタメント・ダウン, xx: デプス(00-FF) Tone portamento, xx: depth (00-FF) トーン・ポルタメント, xx: デプス(00-FF) Vibrato, x: period (0-F), y: depth (0-F) ビブラート, x: ピリオド(0-F), y: デプス(0-F) Tremolo, x: period (0-F), y: depth (0-F) トレモロ, x: ピリオド(0-F), y: デプス(0-F) Pan, xx: 00 = no sound, 01 = right, 02 = left, 03 = center パン, xx: 00 = 無音, 01 = 右, 02 = 左, 03 = 中央 Volume slide, x: up (0-F), y: down (0-F) ボリューム・スライド, x: アップ(0-F), y: ダウン(0-F) Jump to beginning of order xx オーダーxxの先頭へジャンプ End of song ソングの停止 Jump to step xx of next order 次のオーダーのステップxxへジャンプ Change speed (xx: 00-1F), change tempo (xx: 20-FF) スピード変更(xx: 00-1F), テンポ変更(xx: 20-FF) Note delay, xx: count (00-FF) ノート・ディレイ, xx: カウント(00-FF) Auto envelope, x: shift amount (0-F), y: shape (0-F) オートエンベロープ, x: シフト量(x-8), y: エンベロープ形状(0-F) Hardware envelope period 1, xx: high byte (00-FF) ハードウェアエンベロープ周期1, xx: 上位バイト(00-FF) Hardware envelope period 2, xx: low byte (00-FF) ハードウェアエンベロープ周期2, xx: 下位バイト(00-FF) Set groove xx グルーブをxxに設定 Detune, xx: pitch (00-FF) デチューン, xx: ピッチ(00-FF) Note slide up, x: count (0-F), y: seminote (0-F) ノート・スライドアップ, x: カウント(0-F), y: 半音数(0-F) Note slide down, x: count (0-F), y: seminote (0-F) ノート・スライドダウン, x: カウント(0-F), y: 半音数(0-F) Note cut, xx: count (01-FF) ノート・カット, xx: カウント(01-FF) Transpose delay, x: count (1-7: up, 9-F: down), y: seminote (0-F) トランスポーズ・ディレイ, x: カウント(1-7: アップ, 9-F: ダウン), y: 半音数(0-F) Tone/Noise mix, xx: 00 = no sound, 01 = tone, 02 = noise, 03 = tone & noise トーン/ノイズ・ミックス, xx: 00=消音, 01=トーン, 02=ノイズ, 03=トーン & ノイズ Master volume, xx: volume (00-3F) マスターボリューム, xx: 音量(00-3F) Noise pitch, xx: pitch (00-1F) ノイズ・ピッチ, xx: ピッチ(00-1F) Register address bank 0, xx: address (00-6B) レジスタ番地指定 バンク0, xx: 番地(00-B6) Register address bank 1, xx: address (00-6B) レジスタ番地指定 バンク1, xx: 番地(00-B6) Register value set, xx: value (00-FF) レジスタ値指定, xx: レジスタ値(00-FF) AR control, x: operator (1-4), yy: attack rate (00-1F) ARコントロール, x: オペレーター(1-4), yy: AR値(00-1F) Brightness, xx: relative value (01-FF) ブライトネス, xx: 相対値(01-FF) DR control, x: operator (1-4), yy: decay rate (00-1F) DRコントロール, x: オペレーター(1-4), yy: DR値(00-1F) FB control, xx: feedback value (00-07) FBコントロール, xx: フィードバック値(00-07) ML control, x: operator (1-4), y: multiple (0-F) MLコントロール, x: オペレーター(1-4), y: ML値(0-F) Volume delay, x: count (1-F), yy: volume (00-FF) ボリューム・ディレイ, x: カウント(1-F), yy: 音量(00-FF) RR control, x: operator (1-4), y: release rate (0-F) RRコントロール, x: オペレーター(1-4), y: RR値(0-F) TL control, x: operator (1-4), yy: total level (00-7F) TLコントロール, x: オペレーター(1-4), yy: TL値(00-7F) Invalid effect 無効なエフェクト EffectListDialog Effect list エフェクト一覧 Effect エフェクト Track type トラックタイプ Description 説明 FMEnvelopeSetEditDialog Edit FM envelope set FMエンベロープセットの編集 Add 追加 Set digit types in the order of appearence. 数字に対応するパラメーターを出現順に設定します。 Remove 削除 Number 番号 Type タイプ Skip スキップ FMOperatorTable SSGEG Type タイプ Operator %1 オペレーター%1 Copy envelope エンベロープをコピー Paste envelope エンベロープを張り付け Paste envelope From エンベロープを形式貼り付け Copy operator オペレーターをコピー Paste operator オペレーターを張り付け GrooveSettingsDialog Groove Settings グルーブ設定 Remove 削除 Add 追加 Sequence シーケンス Copy Fxx Fxxでコピー Expand 拡大 Generate 生成 Shrink 縮小 Pad パッド Speed: %1 スピード: %1 InstrumentEditorFMForm Envelope エンベロープ Users: ユーザー: LFO/Operator sequence LFO/オペレーターシーケンス LFO Start count: 開始カウント: AM operators AMオペレーター Operator 1 オペレーター1 Operator 2 オペレーター2 Operator 3 オペレーター3 Operator 4 オペレーター4 Envelope reset エンベロープリセット Reset envelope before key on キーオン前にエンベロープを初期化 Operator sequence オペレーターシーケンス Operator: オペレーター: Sequence シーケンス FM 3ch FM 3ch Arpeggio/Pitch アルペジオ/ピッチ Arpeggio アルペジオ Type: タイプ: Pitch ピッチ Absolute 絶対 Freq All Fixed 固定 Relative 相対 Error エラー Did not match the clipboard text format with %1. クリップボードのテキストの形式が%1と一致しません。 Copy envelope エンベロープをコピー Paste envelope エンベロープを張り付け Paste envelope From エンベロープを形式貼り付け Copy LFO parameters LFOパラメーターをコピー Paste LFO parameters LFOパラメーターを張り付け InstrumentEditorSSGForm Waveform 波形 Users: ユーザー: Square mask 矩形波マスク Raw レジスタ値 Tone/Mask ratio トーン/マスク比 Hardware envelope frequency ハードウェアエンベロープ周波数 Tone/Env ratio トーン/ハード比 Tone/Noise トーン/ノイズ Envelope エンベロープ Arpeggio アルペジオ Type: タイプ: Pitch ピッチ Sq Tri Saw InvSaw SMTri SMSaw SMInvSaw HEnv %1 Absolute 絶対 Fixed 固定 Relative 相対 InstrumentSelectionDialog Select instruments インストゥルメント選択 Search... 検索... KeyboardShortcutListDialog Keyboard shortcuts キーボードショートカット一覧 qrc:/doc/shortcuts MainWindow BambooTracker Order List オーダーリスト Module Settings モジュール設定 Copyright 著作権 Author 作曲者 Edit settings エディット設定 # Song Settings ソング設定 Speed スピード Tempo テンポ Untitled 無題 Groove グルーブ Instruments インストゥルメント &File ファイル(&F) &Export エクスポート(&E) &Edit 編集(&E) &Select 選択(&S) Paste Specia&l 特殊貼り付け(&I) &Pattern パターン(&P) &Transpose トランスポーズ(&T) &Module モジュール(&M) Clean&up 最適化(&U) &Instrument インストゥルメント(&I) &Help ヘルプ(&H) &New... 新規作成(&N)... Ctrl+N &Open... 開く(&O)... Ctrl+O &Save 保存(&S) Ctrl+S Save &As... 名前を付けて保存(&A)... E&xit 終了(&X) &Undo 元に戻す(&U) Ctrl+Z &Redo やり直し(&R) Ctrl+Y Cu&t 切り取り(&T) Ctrl+X &Copy コピー(&C) Ctrl+C &Paste 貼り付け(&P) Ctrl+V Ctrl+M &Delete 削除(&D) Pattern size パターン長 Step ステップ Key repetition キー入力繰り返し Title タイトル Songs ソング &Recent Files 最近使ったファイル(&R) Patter&n パターン(&N) Son&g ソング(&G) &Tracker トラッカー(&T) Main toolbar メインツールバー Secondary toolbar サブツールバー Del &All 全選択(&A) Ctrl+A &None 選択解除(&N) Esc E&xpand 拡大(&X) S&hrink 縮小(&H) &Decrease Note 半音を下げる(&D) Ctrl+F1 &Increase Note 半音上げる(&I) Ctrl+F2 D&ecrease Octave 1オクターブ下げる(&E) Ctrl+F3 I&ncrease Octave 1オクターブ上げる(&N) Ctrl+F4 &Insert Order オーダーを挿入(&I) &Remove Order オーダーを削除(&R) &Module Properties... モジュール情報(&M)... Ctrl+P &New Instrument 新規インストゥルメント(&N) &Remove Instrument インストゥルメント削除(&R) &Clone Instrument インストゥルメントをクローン(&C) &Deep Clone Instrument インストゥルメントをディープクローン(&D) &Load From File... ファイルから読み込み(&L)... &Save To File... ファイルへ書き出し(&S)... &Edit... 編集(&E)... Ctrl+I &Play 再生(&P) Play P&attern パターンの初めから再生(&A) F6 Play &From Start 最初から再生(&F) F5 Play From C&ursor カーソル位置から再生(&U) F7 &Stop 停止(&S) F8 &Edit Mode 編集モード(&E) Space To&ggle Track トラックを演奏(&G) Alt+F9 S&olo Track トラックをソロ演奏(&O) Alt+F10 &Kill Sound サウンド初期化(&K) F12 &About... バージョン情報(&A)... Fo&llow Mode フォローモード(&L) ScrollLock &Groove Settings... グルーブ設定(&G)... &Configuration... 設定(&C)... &Duplicate Order オーダーを複製(&D) Ctrl+D Move Order &Up オーダーを上に移動(&U) Move Order Do&wn オーダーを下に移動(&W) &Clone Patterns パターンをクローン(&C) Alt+D Clone &Order オーダーをクローン(&O) &Comments... コメント(&C)... &Interpolate 補完(&P) Ctrl+G &Reverse 反転(&R) Ctrl+R R&eplace Instrument インストゥルメント置換(&E) Alt+S &Row 行(&R) &Column 列(&C) &Order オーダー(&O) Remove Unused &Instruments 未使用のインストゥルメントを削除(&I) Remove Unused &Patterns 未使用のパターンを削除(&P) &WAV... &VGM... &Overwrite Paste Overwrite 上書き(&O) &Clear クリア(&C) &Effect List... エフェクト一覧(&E)... Effect List エフェクト一覧 F1 &Shortcuts... ショートカット一覧(&S)... E&xport To Bank File... バンクファイルへ書き出し(&X)... E&xpand Effect Column エフェクト列の拡張(&X) Alt+L S&hrink Effect Column エフェクト列の縮小(&H) Alt+K Remove &Duplicate Instruments 重複したインストゥルメントの削除(&D) Re&name Instrument 名前を編集(&N) &Mix ミックス(&M) &Import From Bank File... バンクファイルから取り込み(&I)... &S98... Octave オクターブ Octave: %1 Octave: オクターブ: %1 Save changes to %1? Save changes to %1の変更を保存しますか? Error エラー Instrument %1 Instrument インストゥルメント%1 Open instrument インストゥルメントを開く Save instrument インストゥルメント保存 Open bank バンクを開く Select instruments to load: 読み込むインストゥルメントを選択: No instrument インストゥルメントなし Standard 標準 Step highlight 1st ステップハイライト 1st 2nd BambooTracker instrument (*.bti) DefleMask preset (*.dmp) TFM Music Maker instrument (*.tfi) VGM Music Maker instrument (*.vgi) WOPN instrument (*.opni) Gens KMod dump (*.y12) MVSTracker instrument (*.ins) The number of instruments has reached the upper limit. インストゥルメント数が上限に達しています。 BambooTracker bank (*.btb) WOPN bank (*.wopn) Select instruments to save: 保存するインストゥルメントを選択: Save bank バンク保存 - Custom カスタム PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 NEC PC-8801mkIISR FM3ch expanded FM3ch拡張 Could not initialize MIDI input. MIDI Inを初期化できませんでした。 Failed to export to %1. %1の書き出しに失敗しました。 BambooTracker module (*.btm) WAV signed 16-bit PCM (*.wav) VGM file (*.vgm) S98 file (*.s98) Do you want to remove all duplicate instruments? 重複したインストゥルメントを削除しますか? Instrument: %1 Instrument: インストゥルメント: %1 Do you want to change song properties? ソング情報を変更しますか? Change to jam mode ジャムモードに切り替え Change to edit mode 編集モードに切り替え About バージョン情報 Failed to backup module. モジュールのバックアップ作成に失敗しました。 module モジュール s98 S98 vgm VGM wav WAV bank バンク instrument インストゥルメント Failed to load the %1. %1の読み込みに失敗しました。 Failed to save the %1. %1の保存に失敗しました。 Could not load the %1 properly. Please make sure that you have the latest version of BambooTracker. %1を読み込めませんでした。最新版のBambooTrackerで読み込みを行なってください。 Could not load the %1. It may be corrupted. %1を読み込めませんでした。ファイルが破損している可能性があります。 <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018-2020 Rerrah</b><br><hr>Libraries:<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI (SCCI License)<br>- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>Also see changelog which lists contributors. <b>YM2608 (OPNA) Music Tracker<br>Copyright (C) 2018-2020 Rerrah</b><br><hr>使用ライブラリ:<br>- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>- MAME (MAME License)<br>- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>and (C) Jean Pierre Cimalando (LGPL v2.1)<br>- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>- SCCI (SCCI License)<br>- Silk icon set 1.3 by (C) Mark James (CC BY 2.5)<br>- Qt (GPL v2+ or LGPL v3)<br>- VGMPlay by (C) Valley Bell (GPL v2)<br><br>このプロジェクトのコントリビューターについてはChangelogもご覧ください。 Save module モジュール保存 Open module モジュールを開く Do you want to remove all unused instruments? 未使用のインストゥルメントを削除しますか? Do you want to remove all unused patterns? 未使用のパターンを削除しますか? Export to WAV WAV書き出し Cancel キャンセル Export to VGM VGM書き出し Export to S98 S98書き出し Warning 注意 %1 If you execute this command, the command history is reset. %1この操作を実行すると、すべてのコマンド履歴は消去されます。 Could not open the audio stream. Please change the sound settings in Configuration. オーディオストリームを開けませんでした。環境設定でサウンド設定を変更してください。 ModulePropertiesDialog Module properties モジュール情報 Tick frequency ティック周波数 60Hz (NTSC) 50Hz (PAL) Custom カスタム Mixer ミキサー PC-9821 with PC-9801-86 PC-9821 with Speak Board PC-88VA2 PC-8801mkIISR Custom mixer カスタムミキサー FM SSG Set 設定 Song control ソング設定 Remove 削除 Insert song ソング追加 Insert 追加 Untitled 無題 Number 番号 Title タイトル Song type ソングタイプ Standard 標準 FM3ch expanded FM3ch拡張 OrderListPanel &Insert Order オーダーを挿入(&I) &Remove Order オーダーを削除(&R) &Duplicate Order オーダーを複製(&D) &Clone Patterns パターンをクローン(&C) Clone &Order オーダーをクローン(&O) Move Order &Up オーダーを上に移動(&U) Move Order Do&wn Move Order Dow&n オーダーを下に移動(&W) Cop&y コピー(&Y) &Paste 貼り付け(&P) PatternEditorPanel Invalid effect 無効なエフェクト &Undo 元に戻す(&U) &Redo やり直し(&R) &Copy コピー(&C) Cu&t 切り取り(&T) &Paste 貼り付け(&P) Paste Specia&l 特殊貼り付け(&I) &Mix ミックス(&M) &Overwrite 上書き(&O) &Erase 削除(&E) Select &All 全選択(&A) Patter&n パターン(&N) &Interpolate 補完(&P) &Reverse 反転(&R) R&eplace Instrument インストゥルメント置換(&E) E&xpand 拡大(&X) S&hrink 縮小(&H) &Transpose トランスポーズ(&T) &Decrease Note 半音を下げる(&D) &Increase Note 半音上げる(&I) D&ecrease Octave 1オクターブ下げる(&E) I&ncrease Octave 1オクターブ上げる(&N) To&ggle Track トラックを演奏(&G) &Solo Track トラックをソロ演奏(&S) Expand E&ffect Column エフェクト列の拡張(&F) Shrin&k Effect Column エフェクト列の縮小(&K) &Unmute All Tracks 全トラックのミュート解除(&U) QObject Error エラー An unknown error occured. %1 不明なエラーが発生しました。 %1 An unknown error occurred. 不明なエラーが発生しました。 An unknown error occurred. %1 不明なエラーが発生しました。 %1 S98ExportSettingsDialog S98 export settings S98エクスポート設定 Tag タグ Title Title: タイトル Artist Artist: 作曲者 Game Game: ゲーム Year Year: Genre Genre: ジャンル Comment コメント Copyright Copyright: 著作権 S98by S98by: S98作成者 System System: システム NEC PC-9801 Target 出力先音源 FM YM2608 OPNA YM2612 OPN2 YM2203 OPN SSG OPN internal OPN内蔵 AY-3-8910 PSG YM2149 PSG Support 対応リスト FM Channels FMチャンネル数 Yes Drums ドラム ADPCM Resolution 解像度 No ToneNoiseMacroEditor Tone トーン Noise ノイズ VgmExportSettingsDialog VGM export settings VGMエクスポート設定 GD3 tag GD3タグ Game ゲーム Name Name: ゲーム名 English Release date Release date: 発売日 Japanese 日本語 System System: システム NEC PC-9801 Track トラック Title Title: タイトル Author Author: 作曲者 VGM file VGMファイル Creator Creator: 作成者 Notes Notes: その他 Target 出力先音源 FM YM2608 OPNA YM2612 OPN2 YM2203 OPN SSG OPN internal OPN内蔵 AY-3-8910 PSG YM2149 PSG Support 対応リスト FM Channels FMチャンネル数 Yes Drums ドラム ADPCM No VisualizedInstrumentMacroEditor Size: 1 長さ: 1 Size: %1 長さ: %1 Loop ループ Loop %1 Loop ループ%1 Fixed 固定 Release リリース Absolute 絶対 Relative 相対 WaveExportSettingsDialog WAV export settings WAVエクスポート設定 Loop ループ Sample rate サンプルレート BambooTracker-0.3.5/BambooTracker/stream/000077500000000000000000000000001362177441300202505ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/stream/RtAudio/000077500000000000000000000000001362177441300216175ustar00rootroot00000000000000BambooTracker-0.3.5/BambooTracker/stream/RtAudio/RtAudio.cpp000066400000000000000000013274641362177441300237130ustar00rootroot00000000000000/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes Copyright (c) 2001-2019 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /************************************************************************/ // RtAudio: Version 5.1.0 #include "RtAudio.h" #include #include #include #include #include #include // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; const unsigned int RtApi::SAMPLE_RATES[] = { 4000, 5512, 8000, 9600, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; #if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) #include "tchar.h" static std::string convertCharPointerToStdString(const char *text) { return std::string(text); } static std::string convertCharPointerToStdString(const wchar_t *text) { int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::string s( length-1, '\0' ); WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); return s; } #elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) #else #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions #define MUTEX_DESTROY(A) abs(*A) // dummy definitions #endif // *************************************************** // // // RtAudio definitions. // // *************************************************** // std::string RtAudio :: getVersion( void ) { return RTAUDIO_VERSION; } // Define API names and display names. // Must be in same order as API enum. extern "C" { const char* rtaudio_api_names[][2] = { { "unspecified" , "Unknown" }, { "alsa" , "ALSA" }, { "pulse" , "Pulse" }, { "oss" , "OpenSoundSystem" }, { "jack" , "Jack" }, { "core" , "CoreAudio" }, { "wasapi" , "WASAPI" }, { "asio" , "ASIO" }, { "ds" , "DirectSound" }, { "dummy" , "Dummy" }, }; const unsigned int rtaudio_num_api_names = sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); // The order here will control the order of RtAudio's API search in // the constructor. extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__UNIX_JACK__) RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) RtAudio::LINUX_PULSE, #endif #if defined(__LINUX_ALSA__) RtAudio::LINUX_ALSA, #endif #if defined(__LINUX_OSS__) RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) RtAudio::WINDOWS_DS, #endif #if defined(__MACOSX_CORE__) RtAudio::MACOSX_CORE, #endif #if defined(__RTAUDIO_DUMMY__) RtAudio::RTAUDIO_DUMMY, #endif RtAudio::UNSPECIFIED, }; extern "C" const unsigned int rtaudio_num_compiled_apis = sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; } // This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. // If the build breaks here, check that they match. template class StaticAssert { private: StaticAssert() {} }; template<> class StaticAssert{ public: StaticAssert() {} }; class StaticAssertions { StaticAssertions() { StaticAssert(); }}; void RtAudio :: getCompiledApi( std::vector &apis ) { apis = std::vector(rtaudio_compiled_apis, rtaudio_compiled_apis + rtaudio_num_compiled_apis); } std::string RtAudio :: getApiName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return ""; return rtaudio_api_names[api][0]; } std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return "Unknown"; return rtaudio_api_names[api][1]; } RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) { unsigned int i=0; for (i = 0; i < rtaudio_num_compiled_apis; ++i) if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) return rtaudio_compiled_apis[i]; return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new RtApiJack(); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif #if defined(__LINUX_PULSE__) if ( api == LINUX_PULSE ) rtapi_ = new RtApiPulse(); #endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); #endif #if defined(__WINDOWS_ASIO__) if ( api == WINDOWS_ASIO ) rtapi_ = new RtApiAsio(); #endif #if defined(__WINDOWS_WASAPI__) if ( api == WINDOWS_WASAPI ) rtapi_ = new RtApiWasapi(); #endif #if defined(__WINDOWS_DS__) if ( api == WINDOWS_DS ) rtapi_ = new RtApiDs(); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new RtApiCore(); #endif #if defined(__RTAUDIO_DUMMY__) if ( api == RTAUDIO_DUMMY ) rtapi_ = new RtApiDummy(); #endif } RtAudio :: RtAudio( RtAudio::Api api ) { rtapi_ = 0; if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openRtApi( api ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a debug // warning and continue as if no API was specified. std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one device or we reach the end of the list. std::vector< RtAudio::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetDeviceCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll thow an error. std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } RtAudio :: ~RtAudio() { if ( rtapi_ ) delete rtapi_; } void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, userData, options, errorCallback ); } // *************************************************** // // // Public RtApi definitions (see end of file for // private or protected utility functions). // // *************************************************** // RtApi :: RtApi() { stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; stream_.apiHandle = 0; stream_.userBuffer[0] = 0; stream_.userBuffer[1] = 0; MUTEX_INITIALIZE( &stream_.mutex ); showWarnings_ = true; firstErrorOccurred_ = false; } RtApi :: ~RtApi() { MUTEX_DESTROY( &stream_.mutex ); } void RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudio::StreamParameters *iParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; error( RtAudioError::INVALID_USE ); return; } // Clear stream information potentially left from a previously open stream. clearStreamInfo(); if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; error( RtAudioError::INVALID_USE ); return; } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; error( RtAudioError::INVALID_USE ); return; } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; error( RtAudioError::INVALID_USE ); return; } unsigned int nDevices = getDeviceCount(); unsigned int oChannels = 0; if ( oParams ) { oChannels = oParams->nChannels; if ( oParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: output device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } unsigned int iChannels = 0; if ( iParams ) { iChannels = iParams->nChannels; if ( iParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: input device parameter value is invalid."; error( RtAudioError::INVALID_USE ); return; } } bool result; if ( oChannels > 0 ) { result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { error( RtAudioError::SYSTEM_ERROR ); return; } } if ( iChannels > 0 ) { result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) { if ( oChannels > 0 ) closeStream(); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; } unsigned int RtApi :: getDefaultInputDevice( void ) { // Should be implemented in subclasses if possible. return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { // Should be implemented in subclasses if possible. return 0; } void RtApi :: closeStream( void ) { // MUST be implemented in subclasses! return; } bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { // MUST be implemented in subclasses! return FAILURE; } void RtApi :: tickStreamTime( void ) { // Subclasses that do not provide their own implementation of // getStreamTime should call this function once per buffer I/O to // provide basic stream time support. stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif } long RtApi :: getStreamLatency( void ) { verifyStream(); long totalLatency = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) totalLatency = stream_.latency[0]; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) totalLatency += stream_.latency[1]; return totalLatency; } double RtApi :: getStreamTime( void ) { verifyStream(); #if defined( HAVE_GETTIMEOFDAY ) // Return a very accurate estimate of the stream time by // adding in the elapsed time since the last tick. struct timeval then; struct timeval now; if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) return stream_.streamTime; gettimeofday( &now, NULL ); then = stream_.lastTickTimestamp; return stream_.streamTime + ((now.tv_sec + 0.000001 * now.tv_usec) - (then.tv_sec + 0.000001 * then.tv_usec)); #else return stream_.streamTime; #endif } void RtApi :: setStreamTime( double time ) { verifyStream(); if ( time >= 0.0 ) stream_.streamTime = time; #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif } unsigned int RtApi :: getStreamSampleRate( void ) { verifyStream(); return stream_.sampleRate; } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it // cannot be guaranteed to always behave correctly because we cannot // synchronize these two callbacks. // // A property listener is installed for over/underrun information. // However, no functionality is currently provided to allow property // listeners to trigger user handlers because it is unclear what could // be done if a critical stream parameter (buffer size, sample rate, // device disconnect) notification arrived. The listeners entail // quite a bit of extra code and most likely, a user program wouldn't // be prepared for the result anyway. However, we do provide a flag // to the client callback function to inform of an over/underrun. // A structure to hold various information related to the CoreAudio API // implementation. struct CoreHandle { AudioDeviceID id[2]; // device ids #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceIOProcID procId[2]; #endif UInt32 iStream[2]; // device stream index (or first if using multiple) UInt32 nStreams[2]; // number of streams to use bool xrun[2]; char *deviceBuffer; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. CoreHandle() :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) // This is a largely undocumented but absolutely necessary // requirement starting with OS-X 10.6. If not called, queries and // updates to various audio device properties are not handled // correctly. CFRunLoopRef theRunLoop = NULL; AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; error( RtAudioError::WARNING ); } #endif } RtApiCore :: ~RtApiCore() { // The subclass destructor gets called before the base class // destructor, so close an existing stream before deallocating // apiDeviceId memory. if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiCore :: getDeviceCount( void ) { // Find out how many audio devices there are, if any. UInt32 dataSize; AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; error( RtAudioError::WARNING ); return 0; } return dataSize / sizeof( AudioDeviceID ); } unsigned int RtApiCore :: getDefaultInputDevice( void ) { unsigned int nDevices = getDeviceCount(); if ( nDevices <= 1 ) return 0; AudioDeviceID id; UInt32 dataSize = sizeof( AudioDeviceID ); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; error( RtAudioError::WARNING ); return 0; } dataSize *= nDevices; AudioDeviceID deviceList[ nDevices ]; property.mSelector = kAudioHardwarePropertyDevices; result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return 0; } for ( unsigned int i=0; i= nDevices ) { errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; error( RtAudioError::WARNING ); return info; } AudioDeviceID id = deviceList[ device ]; // Get the device name. info.name.erase(); CFStringRef cfname; dataSize = sizeof( CFStringRef ); property.mSelector = kAudioObjectPropertyManufacturer; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); int length = CFStringGetLength(cfname); char *mname = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)mname, strlen(mname) ); info.name.append( ": " ); CFRelease( cfname ); free(mname); property.mSelector = kAudioObjectPropertyName; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); length = CFStringGetLength(cfname); char *name = (char *)malloc(length * 3 + 1); #if defined( UNICODE ) || defined( _UNICODE ) CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); #else CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); #endif info.name.append( (const char *)name, strlen(name) ); CFRelease( cfname ); free(name); // Get the output stream "configuration". AudioBufferList *bufferList = nil; property.mSelector = kAudioDevicePropertyStreamConfiguration; property.mScope = kAudioDevicePropertyScopeOutput; // property.mElement = kAudioObjectPropertyElementWildcard; dataSize = 0; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if ( result != noErr || dataSize == 0 ) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get output channel information. unsigned int i, nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // Get the input stream "configuration". property.mScope = kAudioDevicePropertyScopeInput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; error( RtAudioError::WARNING ); return info; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Probe the device sample rates. bool isInput = false; if ( info.outputChannels == 0 ) isInput = true; // Determine the supported sample rates. property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } UInt32 nRanges = dataSize / sizeof( AudioValueRange ); AudioValueRange rangeList[ nRanges ]; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The sample rate reporting mechanism is a bit of a mystery. It // seems that it can either return individual rates or a range of // rates. I assume that if the min / max range values are the same, // then that represents a single supported rate and if the min / max // range values are different, the device supports an arbitrary // range of values (though there might be multiple ranges, so we'll // use the most conservative range). Float64 minimumRate = 1.0, maximumRate = 10000000000.0; bool haveValueRange = false; info.sampleRates.clear(); for ( UInt32 i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = tmpSr; } else { haveValueRange = true; if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; } } if ( haveValueRange ) { for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } // Sort and remove any redundant values std::sort( info.sampleRates.begin(), info.sampleRates.end() ); info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // CoreAudio always uses 32-bit floating point data for PCM streams. // Thus, any other "physical" formats supported by the device are of // no interest to the client. info.nativeFormats = RTAUDIO_FLOAT32; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; return info; } static OSStatus callbackHandler( AudioDeviceID inDevice, const AudioTimeStamp* /*inNow*/, const AudioBufferList* inInputData, const AudioTimeStamp* /*inInputTime*/, AudioBufferList* outOutputData, const AudioTimeStamp* /*inOutputTime*/, void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiCore *object = (RtApiCore *) info->object; if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) return kAudioHardwareUnspecifiedError; else return kAudioHardwareNoError; } static OSStatus xrunListener( AudioObjectID /*inDevice*/, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; ixrun[1] = true; else handle->xrun[0] = true; } } return kAudioHardwareNoError; } static OSStatus rateListener( AudioObjectID inDevice, UInt32 /*nAddresses*/, const AudioObjectPropertyAddress /*properties*/[], void* ratePointer ) { Float64 *rate = (Float64 *) ratePointer; UInt32 dataSize = sizeof( Float64 ); AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); return kAudioHardwareNoError; } bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; return FAILURE; } AudioDeviceID deviceList[ nDevices ]; UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; return FAILURE; } AudioDeviceID id = deviceList[ device ]; // Setup for stream mode. bool isInput = false; if ( mode == INPUT ) { isInput = true; property.mScope = kAudioDevicePropertyScopeInput; } else property.mScope = kAudioDevicePropertyScopeOutput; // Get the stream "configuration". AudioBufferList *bufferList = nil; dataSize = 0; property.mSelector = kAudioDevicePropertyStreamConfiguration; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; return FAILURE; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Search for one or more streams that contain the desired number of // channels. CoreAudio devices can have an arbitrary number of // streams and each stream can have an arbitrary number of channels. // For each stream, a single buffer of interleaved samples is // provided. RtAudio prefers the use of one stream of interleaved // data or multiple consecutive single-channel streams. However, we // now support multiple consecutive multi-channel streams of // interleaved data as well. UInt32 iStream, offsetCounter = firstChannel; UInt32 nStreams = bufferList->mNumberBuffers; bool monoMode = false; bool foundStream = false; // First check that the device supports the requested number of // channels. UInt32 deviceChannels = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( deviceChannels < ( channels + firstChannel ) ) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; errorText_ = errorStream_.str(); return FAILURE; } // Look for a single stream meeting our needs. UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels >= channels + offsetCounter ) { firstStream = iStream; channelOffset = offsetCounter; foundStream = true; break; } if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } // If we didn't find a single stream above, then we should be able // to meet the channel specification with multiple streams. if ( foundStream == false ) { monoMode = true; offsetCounter = firstChannel; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } firstStream = iStream; channelOffset = offsetCounter; Int32 channelCounter = channels + offsetCounter - streamChannels; if ( streamChannels > 1 ) monoMode = false; while ( channelCounter > 0 ) { streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; if ( streamChannels > 1 ) monoMode = false; channelCounter -= streamChannels; streamCount++; } } free( bufferList ); // Determine the buffer size. AudioValueRange bufferRange; dataSize = sizeof( AudioValueRange ); property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; // Set the buffer size. For multiple streams, I'm assuming we only // need to make this setting for the master channel. UInt32 theSize = (UInt32) *bufferSize; dataSize = sizeof( UInt32 ); property.mSelector = kAudioDevicePropertyBufferFrameSize; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! *bufferSize = theSize; if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; // Try to set "hog" mode ... it's not clear to me this is working. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { pid_t hog_pid; dataSize = sizeof( hog_pid ); property.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } if ( hog_pid != getpid() ) { hog_pid = getpid(); result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } } } // Check and if necessary, change the sample rate for the device. Float64 nominalRate; dataSize = sizeof( Float64 ); property.mSelector = kAudioDevicePropertyNominalSampleRate; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; errorText_ = errorStream_.str(); return FAILURE; } // Only change the sample rate if off by more than 1 Hz. if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { // Set a property listener for the sample rate change Float64 reportedRate = 0.0; AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } nominalRate = (Float64) sampleRate; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); if ( result != noErr ) { AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Now wait until the reported nominal rate is what we just set. UInt32 microCounter = 0; while ( reportedRate != nominalRate ) { microCounter += 5000; if ( microCounter > 5000000 ) break; usleep( 5000 ); } // Remove the property listener. AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( microCounter > 5000000 ) { errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now set the stream format for all streams. Also, check the // physical format of the device and change that if necessary. AudioStreamBasicDescription description; dataSize = sizeof( AudioStreamBasicDescription ); property.mSelector = kAudioStreamPropertyVirtualFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Set the sample rate and data format id. However, only make the // change if the sample rate is not within 1.0 of the desired // rate and the format is not linear pcm. bool updateFormat = false; if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { description.mSampleRate = (Float64) sampleRate; updateFormat = true; } if ( description.mFormatID != kAudioFormatLinearPCM ) { description.mFormatID = kAudioFormatLinearPCM; updateFormat = true; } if ( updateFormat ) { result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now check the physical format. property.mSelector = kAudioStreamPropertyPhysicalFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } //std::cout << "Current physical stream format:" << std::endl; //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; //std::cout << " sample rate = " << description.mSampleRate << std::endl; if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { description.mFormatID = kAudioFormatLinearPCM; //description.mSampleRate = (Float64) sampleRate; AudioStreamBasicDescription testDescription = description; UInt32 formatFlags; // We'll try higher bit rates first and then work our way down. std::vector< std::pair > physicalFormats; formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; physicalFormats.push_back( std::pair( 32, formatFlags ) ); formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 32, formatFlags ) ); physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low formatFlags |= kAudioFormatFlagIsAlignedHigh; physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 16, formatFlags ) ); physicalFormats.push_back( std::pair( 8, formatFlags ) ); bool setPhysicalFormat = false; for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( streamCount == 1 ) { if ( stream_.nUserChannels[mode] > 1 && stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; } else if ( monoMode && stream_.userInterleaved ) stream_.doConvertBuffer[mode] = true; // Allocate our CoreHandle structure for the stream. CoreHandle *handle = 0; if ( stream_.apiHandle == 0 ) { try { handle = new CoreHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; goto error; } if ( pthread_cond_init( &handle->condition, NULL ) ) { errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else handle = (CoreHandle *) stream_.apiHandle; handle->iStream[mode] = firstStream; handle->nStreams[mode] = streamCount; handle->id[mode] = id; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; goto error; } // If possible, we will make use of the CoreAudio stream buffers as // "device buffers". However, we can't do this if using multiple // streams. if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) { if ( streamCount > 1 ) setConvertInfo( mode, 0 ); else setConvertInfo( mode, channelOffset ); } if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) // Only one callback procedure per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); #else // deprecated in favor of AudioDeviceCreateIOProcID() result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; errorText_ = errorStream_.str(); goto error; } if ( stream_.mode == OUTPUT && mode == INPUT ) stream_.mode = DUPLEX; else stream_.mode = mode; } // Setup the device property listener for over/underload. property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } } if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], callbackHandler ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); #else // deprecated in favor of AudioDeviceDestroyIOProcID() AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { if (handle) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } } if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); #else // deprecated in favor of AudioDeviceDestroyIOProcID() AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // Destroy pthread condition variable. pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiCore :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiCore::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = AudioDeviceStart( handle->id[0], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { result = AudioDeviceStart( handle->id[1], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } result = AudioDeviceStop( handle->id[0], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { result = AudioDeviceStop( handle->id[1], callbackHandler ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } stream_.state = STREAM_STOPPED; unlock: if ( result == noErr ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the // callbackEvent() function probably should return before the AudioDeviceStop() // function is called. static void *coreStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiCore *object = (RtApiCore *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, coreStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are // draining stream or duplex mode AND the input/output devices are // different AND this function is called for the input device. if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream if ( handle->nStreams[0] == 1 ) { memset( outBufferList->mBuffers[handle->iStream[0]].mData, 0, outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } else { // fill multiple streams with zeros for ( unsigned int i=0; inStreams[0]; i++ ) { memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, 0, outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); } } } else if ( handle->nStreams[0] == 1 ) { if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], stream_.convertInfo[0] ); } else { // copy from user buffer memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } } else { // fill multiple streams Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); inBuffer = (Float32 *) stream_.deviceBuffer; } if ( stream_.deviceInterleaved[0] == false ) { // mono mode UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); } } else { // fill multiple multi-channel streams with interleaved data UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; Float32 *out, *in; bool inInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 inChannels = stream_.nUserChannels[0]; if ( stream_.doConvertBuffer[0] ) { inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode inChannels = stream_.nDeviceChannels[0]; } if ( inInterleaved ) inOffset = 1; else inOffset = stream_.bufferSize; channelsLeft = inChannels; for ( unsigned int i=0; inStreams[0]; i++ ) { in = inBuffer; out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; outJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[0] > 0 ) { streamChannels -= stream_.channelOffset[0]; outJump = stream_.channelOffset[0]; out += outJump; } // Account for possible unfilled channels at end of the last stream if ( streamChannels > channelsLeft ) { outJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine input buffer offsets and skips if ( inInterleaved ) { inJump = inChannels; in += inChannels - channelsLeft; } else { inJump = 1; in += (inChannels - channelsLeft) * inOffset; } for ( unsigned int i=0; idrainCounter ) { handle->drainCounter++; goto unlock; } AudioDeviceID inputDevice; inputDevice = handle->id[1]; if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { if ( handle->nStreams[1] == 1 ) { if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer convertBuffer( stream_.userBuffer[1], (char *) inBufferList->mBuffers[handle->iStream[1]].mData, stream_.convertInfo[1] ); } else { // copy to user buffer memcpy( stream_.userBuffer[1], inBufferList->mBuffers[handle->iStream[1]].mData, inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); } } else { // read from multiple streams Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; if ( stream_.deviceInterleaved[1] == false ) { // mono mode UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); } } else { // read from multiple multi-channel streams UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; Float32 *out, *in; bool outInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 outChannels = stream_.nUserChannels[1]; if ( stream_.doConvertBuffer[1] ) { outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode outChannels = stream_.nDeviceChannels[1]; } if ( outInterleaved ) outOffset = 1; else outOffset = stream_.bufferSize; channelsLeft = outChannels; for ( unsigned int i=0; inStreams[1]; i++ ) { out = outBuffer; in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; inJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[1] > 0 ) { streamChannels -= stream_.channelOffset[1]; inJump = stream_.channelOffset[1]; in += inJump; } // Account for possible unread channels at end of the last stream if ( streamChannels > channelsLeft ) { inJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine output buffer offsets and skips if ( outInterleaved ) { outJump = outChannels; out += outChannels - channelsLeft; } else { outJump = 1; out += (outChannels - channelsLeft) * outOffset; } for ( unsigned int i=0; iid[0] != handle->id[1] && deviceId == handle->id[0] ) ) RtApi::tickStreamTime(); return SUCCESS; } const char* RtApiCore :: getErrorCode( OSStatus code ) { switch( code ) { case kAudioHardwareNotRunningError: return "kAudioHardwareNotRunningError"; case kAudioHardwareUnspecifiedError: return "kAudioHardwareUnspecifiedError"; case kAudioHardwareUnknownPropertyError: return "kAudioHardwareUnknownPropertyError"; case kAudioHardwareBadPropertySizeError: return "kAudioHardwareBadPropertySizeError"; case kAudioHardwareIllegalOperationError: return "kAudioHardwareIllegalOperationError"; case kAudioHardwareBadObjectError: return "kAudioHardwareBadObjectError"; case kAudioHardwareBadDeviceError: return "kAudioHardwareBadDeviceError"; case kAudioHardwareBadStreamError: return "kAudioHardwareBadStreamError"; case kAudioHardwareUnsupportedOperationError: return "kAudioHardwareUnsupportedOperationError"; case kAudioDeviceUnsupportedFormatError: return "kAudioDeviceUnsupportedFormatError"; case kAudioDevicePermissionsError: return "kAudioDevicePermissionsError"; default: return "CoreAudio unknown error"; } } //******************** End of __MACOSX_CORE__ *********************// #endif #if defined(__UNIX_JACK__) // JACK is a low-latency audio server, originally written for the // GNU/Linux operating system and now also ported to OS-X. It can // connect a number of different applications to an audio device, as // well as allowing them to share audio between themselves. // // When using JACK with RtAudio, "devices" refer to JACK clients that // have ports connected to the server. The JACK server is typically // started in a terminal as follows: // // .jackd -d alsa -d hw:0 // // or through an interface program such as qjackctl. Many of the // parameters normally set for a stream are fixed by the JACK server // and can be specified when the JACK server is started. In // particular, // // .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4 // // specifies a sample rate of 44100 Hz, a buffer size of 512 sample // frames, and number of buffers = 4. Once the server is running, it // is not possible to override these values. If the values are not // specified in the command-line, the JACK server uses default values. // // The JACK server does not have to be running when an instance of // RtApiJack is created, though the function getDeviceCount() will // report 0 devices found until JACK has been started. When no // devices are available (i.e., the JACK server is not running), a // stream cannot be opened. #include #include #include // A structure to hold various information related to the Jack API // implementation. struct JackHandle { jack_client_t *client; jack_port_t **ports[2]; std::string deviceName[2]; bool xrun[2]; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. JackHandle() :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; #if !defined(__RTAUDIO_DEBUG__) static void jackSilentError( const char * ) {}; #endif RtApiJack :: RtApiJack() :shouldAutoconnect_(true) { // Nothing to do here. #if !defined(__RTAUDIO_DEBUG__) // Turn off Jack's internal error reporting. jack_set_error_function( &jackSilentError ); #endif } RtApiJack :: ~RtApiJack() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiJack :: getDeviceCount( void ) { // See if we can become a jack client. jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); if ( client == 0 ) return 0; const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nChannels ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon + 1 ); if ( port != previousPort ) { nDevices++; previousPort = port; } } } while ( ports[++nChannels] ); free( ports ); } jack_client_close( client ); return nDevices; } RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); if ( client == 0 ) { errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return info; } const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) info.name = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { jack_client_close( client ); errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // Get the current jack server sample rate. info.sampleRates.clear(); info.preferredSampleRate = jack_get_sample_rate( client ); info.sampleRates.push_back( info.preferredSampleRate ); // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.outputChannels = nChannels; } // Jack "output ports" equal RtAudio input channels. nChannels = 0; ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); info.inputChannels = nChannels; } if ( info.outputChannels == 0 && info.inputChannels == 0 ) { jack_client_close(client); errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; error( RtAudioError::WARNING ); return info; } // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Jack always uses 32-bit floats. info.nativeFormats = RTAUDIO_FLOAT32; // Jack doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; jack_client_close(client); info.probed = true; return info; } static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; return 0; } // This function will be called by a spawned thread when the Jack // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->closeStream(); pthread_exit( NULL ); } static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; // Check current stream state. If stopped, then we'll assume this // was called as a result of a call to RtApiJack::stopStream (the // deactivation of a client handle causes this function to be called). // If not, we'll assume the Jack server is shutting down or some // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; } static int jackXrun( void *infoPointer ) { JackHandle *handle = *((JackHandle **) infoPointer); if ( handle->ports[0] ) handle->xrun[0] = true; if ( handle->ports[1] ) handle->xrun[1] = true; return 0; } bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { JackHandle *handle = (JackHandle *) stream_.apiHandle; // Look for jack server and try to become a client (only do once per stream). jack_client_t *client = 0; if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; if ( options && !options->streamName.empty() ) client = jack_client_open( options->streamName.c_str(), jackoptions, status ); else client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; error( RtAudioError::WARNING ); return FAILURE; } } else { // The handle must have been created on an earlier pass. client = handle->client; } const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nPorts ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { if ( nDevices == device ) deviceName = port; nDevices++; previousPort = port; } } } while ( ports[++nPorts] ); free( ports ); } if ( device >= nDevices ) { errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; return FAILURE; } unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); } // Compare the jack ports for specified client to the requested number of channels. if ( nChannels < (channels + firstChannel) ) { errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Check the jack server sample rate. unsigned int jackRate = jack_get_sample_rate( client ); if ( sampleRate != jackRate ) { jack_client_close( client ); errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = jackRate; // Get the latency of the JACK port. ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); // the range (usually the min and max are equal) jack_latency_range_t latrange; latrange.min = latrange.max = 0; // get the latency range jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); // be optimistic, use the min! stream_.latency[mode] = latrange.min; //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); } free( ports ); // The jack server always uses 32-bit floating-point data. stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; stream_.userFormat = format; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Jack always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Jack always provides host byte-ordered data. stream_.doByteSwap[mode] = false; // Get the buffer size. The buffer size and number of buffers // (periods) is set when the jack server is started. stream_.bufferSize = (int) jack_get_buffer_size( client ); *bufferSize = stream_.bufferSize; stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate our JackHandle structure for the stream. if ( handle == 0 ) { try { handle = new JackHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; goto error; } if ( pthread_cond_init(&handle->condition, NULL) ) { errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; handle->client = client; } handle->deviceName[mode] = deviceName; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; if ( mode == OUTPUT ) bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); else { // mode == INPUT bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); if ( bufferBytes < bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate memory for the Jack ports (channels) identifiers. handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); if ( handle->ports[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; goto error; } stream_.device[mode] = device; stream_.channelOffset[mode] = firstChannel; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up the stream for output. stream_.mode = DUPLEX; else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); } // Register our ports. char label[64]; if ( mode == OUTPUT ) { for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } } else { for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); } } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); if ( options && options->flags & RTAUDIO_JACK_DONT_CONNECT ) shouldAutoconnect_ = false; return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); jack_client_close( handle->client ); if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( handle ) { if ( stream_.state == STREAM_RUNNING ) jack_deactivate( handle->client ); jack_client_close( handle->client ); } if ( handle ) { if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiJack :: startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; goto unlock; } const char **ports; // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; } // Now make the port connections. Since RtAudio wasn't designed to // allow the user to select particular channels of a device, we'll // just open the first "nChannels" ports with offset. for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting output ports!"; goto unlock; } } free(ports); } if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { result = 1; ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; } // Now make the port connections. See note above. for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting input ports!"; goto unlock; } } free(ports); } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiJack :: stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } } jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; } void RtApiJack :: abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; JackHandle *handle = (JackHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else pthread_cond_signal( &handle->condition ); return SUCCESS; } // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } jack_default_audio_sample_t *jackbuffer; unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memset( jackbuffer, 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); } } else { // no buffer conversion for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[1] ) { for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); } convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { // no buffer conversion for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); } } } unlock: RtApi::tickStreamTime(); return SUCCESS; } //******************** End of __UNIX_JACK__ *********************// #endif #if defined(__WINDOWS_ASIO__) // ASIO API on Windows // The ASIO API is designed around a callback scheme, so this // implementation is similar to that used for OS-X CoreAudio and Linux // Jack. The primary constraint with ASIO is that it only allows // access to a single driver at a time. Thus, it is not possible to // have more than one simultaneous RtAudio stream. // // This implementation also requires a number of external ASIO files // and a few global variables. The ASIO callback scheme does not // allow for the passing of user data, so we must create a global // pointer to our callbackInfo structure. // // On unix systems, we make use of a pthread condition variable. // Since there is no equivalent in Windows, I hacked something based // on information found in // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. #include "asiosys.h" #include "asio.h" #include "iasiothiscallresolver.h" #include "asiodrivers.h" #include static AsioDrivers drivers; static ASIOCallbacks asioCallbacks; static ASIODriverInfo driverInfo; static CallbackInfo *asioCallbackInfo; static bool asioXRun; struct AsioHandle { int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. ASIOBufferInfo *bufferInfos; HANDLE condition; AsioHandle() :drainCounter(0), internalDrain(false), bufferInfos(0) {} }; // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); static void sampleRateChanged( ASIOSampleRate sRate ); static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { // ASIO cannot run on a multi-threaded appartment. You can call // CoInitialize beforehand, but it must be for appartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RtAudioError::WARNING ); } coInitialized_ = true; drivers.removeCurrentDriver(); driverInfo.asioVersion = 2; // See note in DirectSound implementation about GetDesktopWindow(). driverInfo.sysRef = GetForegroundWindow(); } RtApiAsio :: ~RtApiAsio() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); } unsigned int RtApiAsio :: getDeviceCount( void ) { return (unsigned int) drivers.asioGetNumDev(); } RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; // Get device ID unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } // If a stream is already open, we cannot probe other devices. Thus, use the saved results. if ( stream_.state != STREAM_CLOSED ) { if ( device >= devices_.size() ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } char driverName[32]; ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.name = driverName; if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Determine the device channel information. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.outputChannels = outputChannels; info.inputChannels = inputChannels; if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Determine the supported sample rates. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } // Determine supported data types ... just check first channel and assume rest are the same. ASIOChannelInfo channelInfo; channelInfo.channel = 0; channelInfo.isInput = true; if ( info.inputChannels <= 0 ) channelInfo.isInput = false; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } info.nativeFormats = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) info.nativeFormats |= RTAUDIO_SINT16; else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) info.nativeFormats |= RTAUDIO_SINT32; else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) info.nativeFormats |= RTAUDIO_SINT24; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; info.probed = true; drivers.removeCurrentDriver(); return info; } static void bufferSwitch( long index, ASIOBool /*processNow*/ ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); } void RtApiAsio :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; isaveDeviceInfo(); if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // keep them before any "goto error", they are used for error cleanup + goto device boundary checks bool buffersAllocated = false; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; unsigned int nChannels; // Check the device channel count. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; errorText_ = errorStream_.str(); goto error; } stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; // Verify the sample rate is supported. result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } // Get the current sample rate ASIOSampleRate currentRate; result = ASIOGetSampleRate( ¤tRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; errorText_ = errorStream_.str(); goto error; } // Set the sample rate only if necessary if ( currentRate != sampleRate ) { result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } } // Determine the driver data type. ASIOChannelInfo channelInfo; channelInfo.channel = 0; if ( mode == OUTPUT ) channelInfo.isInput = false; else channelInfo.isInput = true; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; errorText_ = errorStream_.str(); goto error; } // Assuming WINDOWS host is always little-endian. stream_.doByteSwap[mode] = false; stream_.userFormat = format; stream_.deviceFormat[mode] = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; } if ( stream_.deviceFormat[mode] == 0 ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); goto error; } // Set the buffer size. For a duplex stream, this will end up // setting the buffer size based on the input constraints, which // should be ok. long minSize, maxSize, preferSize, granularity; result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; errorText_ = errorStream_.str(); goto error; } if ( isDuplexInput ) { // When this is the duplex input (output was opened before), then we have to use the same // buffersize as the output, because it might use the preferred buffer size, which most // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, // So instead of throwing an error, make them equal. The caller uses the reference // to the "bufferSize" param as usual to set up processing buffers. *bufferSize = stream_.bufferSize; } else { if ( *bufferSize == 0 ) *bufferSize = preferSize; else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; else if ( granularity == -1 ) { // Make sure bufferSize is a power of two. int log2_of_min_size = 0; int log2_of_max_size = 0; for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { if ( minSize & ((long)1 << i) ) log2_of_min_size = i; if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; } long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); int min_delta_num = log2_of_min_size; for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); if (current_delta < min_delta) { min_delta = current_delta; min_delta_num = i; } } *bufferSize = ( (unsigned int)1 << min_delta_num ); if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; } else if ( granularity != 0 ) { // Set to an even multiple of granularity, rounding up. *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; } } /* // we don't use it anymore, see above! // Just left it here for the case... if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; goto error; } */ stream_.bufferSize = *bufferSize; stream_.nBuffers = 2; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // ASIO always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Allocate, if necessary, our AsioHandle structure for the stream. if ( handle == 0 ) { try { handle = new AsioHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } handle->bufferInfos = 0; // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } // Create the ASIO internal buffers. Since RtAudio sets up input // and output separately, we'll have to dispose of previously // created output buffers for a duplex stream. if ( mode == INPUT && stream_.mode == OUTPUT ) { ASIODisposeBuffers(); if ( handle->bufferInfos ) free( handle->bufferInfos ); } // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. unsigned int i; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); if ( handle->bufferInfos == NULL ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } ASIOBufferInfo *infos; infos = handle->bufferInfos; for ( i=0; iisInput = ASIOFalse; infos->channelNum = i + stream_.channelOffset[0]; infos->buffers[0] = infos->buffers[1] = 0; } for ( i=0; iisInput = ASIOTrue; infos->channelNum = i + stream_.channelOffset[1]; infos->buffers[0] = infos->buffers[1] = 0; } // prepare for callbacks stream_.sampleRate = sampleRate; stream_.device[mode] = device; stream_.mode = isDuplexInput ? DUPLEX : mode; // store this class instance before registering callbacks, that are going to use it asioCallbackInfo = &stream_.callbackInfo; stream_.callbackInfo.object = (void *) this; // Set up the ASIO callback structure and create the ASIO data buffers. asioCallbacks.bufferSwitch = &bufferSwitch; asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = NULL; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). // In that case, let's be naïve and try that instead. *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); } if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; errorText_ = errorStream_.str(); goto error; } buffersAllocated = true; stream_.state = STREAM_STOPPED; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( isDuplexInput && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Determine device latencies long inputLatency, outputLatency; result = ASIOGetLatencies( &inputLatency, &outputLatency ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; stream_.latency[1] = inputLatency; } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); return SUCCESS; error: if ( !isDuplexInput ) { // the cleanup for error in the duplex input, is done by RtApi::openStream // So we clean up for single channel only if ( buffersAllocated ) ASIODisposeBuffers(); drivers.removeCurrentDriver(); if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } if ( stream_.userBuffer[mode] ) { free( stream_.userBuffer[mode] ); stream_.userBuffer[mode] = 0; } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } } return FAILURE; }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; ASIOStop(); } ASIODisposeBuffers(); drivers.removeCurrentDriver(); AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } bool stopThreadCalled = false; void RtApiAsio :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; errorText_ = errorStream_.str(); goto unlock; } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; asioXRun = false; unlock: stopThreadCalled = false; if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } } stream_.state = STREAM_STOPPED; ASIOError result = ASIOStop(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; errorText_ = errorStream_.str(); } if ( result == ASE_OK ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } // The following lines were commented-out because some behavior was // noted where the device buffers need to be zeroed to avoid // continuing sound, even when the device buffers are completely // disposed. So now, calling abort is the same as calling stop. // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // handle->drainCounter = 2; stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the ASIOStop() // function will return. static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; object->stopStream(); _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && asioXRun == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; asioXRun = false; } if ( stream_.mode != OUTPUT && asioXRun == true ) { status |= RTAUDIO_INPUT_OVERFLOW; asioXRun = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } unsigned int nChannels, bufferBytes, i, j; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[0], stream_.deviceFormat[0] ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); } } else { if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.userBuffer[0], stream_.bufferSize * stream_.nUserChannels[0], stream_.userFormat ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); if (stream_.doConvertBuffer[1]) { // Always interleave ASIO input data. for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) memcpy( &stream_.deviceBuffer[j++*bufferBytes], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[1], stream_.deviceFormat[1] ); convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { memcpy( &stream_.userBuffer[1][bufferBytes*j++], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.userBuffer[1], stream_.bufferSize * stream_.nUserChannels[1], stream_.userFormat ); } } unlock: // The following call was suggested by Malte Clasen. While the API // documentation indicates it should not be required, some device // drivers apparently do not function correctly without it. ASIOOutputReady(); RtApi::tickStreamTime(); return SUCCESS; } static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, // actual sample rate might not have even changed, maybe only the // sample rate status of an AES/EBU or S/PDIF digital input at the // audio device. RtApi *object = (RtApi *) asioCallbackInfo->object; try { object->stopStream(); } catch ( RtAudioError &exception ) { std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; return; } std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; switch( selector ) { case kAsioSelectorSupported: if ( value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged // The following three were added for ASIO 2.0, you don't // necessarily have to support them. || value == kAsioSupportsTimeInfo || value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor) ret = 1L; break; case kAsioResetRequest: // Defer the task and perform the reset of the driver during the // next "safe" situation. You cannot reset the driver right now, // as this code is called from the driver. Reset the driver is // done by completely destruct is. I.e. ASIOStop(), // ASIODisposeBuffers(), Destruction Afterwards you initialize the // driver again. std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; ret = 1L; break; case kAsioResyncRequest: // This informs the application that the driver encountered some // non-fatal data loss. It is used for synchronization purposes // of different media. Added mainly to work around the Win16Mutex // problems in Windows 95/98 with the Windows Multimedia system, // which could lose data because the Mutex was held too long by // another thread. However a driver can issue it in other // situations, too. // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; asioXRun = true; ret = 1L; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were // latencies changed. Beware, it this does not mean that the // buffer sizes have changed! You might need to update internal // delay data. std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; ret = 1L; break; case kAsioEngineVersion: // Return the supported ASIO version of the host application. If // a host application does not implement this selector, ASIO 1.0 // is assumed by the driver. ret = 2L; break; case kAsioSupportsTimeInfo: // Informs the driver whether the // asioCallbacks.bufferSwitchTimeInfo() callback is supported. // For compatibility with ASIO 1.0 drivers the host application // should always support the "old" bufferSwitch method, too. ret = 0; break; case kAsioSupportsTimeCode: // Informs the driver whether application is interested in time // code info. If an application does not need to know about time // code, the driver has less work to do. ret = 0; break; } return ret; } static const char* getAsioErrorString( ASIOError result ) { struct Messages { ASIOError value; const char*message; }; static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, { ASE_InvalidParameter, "Invalid input parameter." }, { ASE_InvalidMode, "Invalid mode." }, { ASE_SPNotAdvancing, "Sample position not advancing." }, { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, { ASE_NoMemory, "Not enough memory to complete the request." } }; for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) if ( m[i].value == result ) return m[i].message; return "Unknown error."; } //******************** End of __WINDOWS_ASIO__ *********************// #endif #if defined(__WINDOWS_WASAPI__) // Windows WASAPI API // Authored by Marcus Tomlinson , April 2014 // - Introduces support for the Windows WASAPI API // - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required // - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface // - Includes automatic internal conversion of sample rate and buffer size between hardware and the user #ifndef INITGUID #define INITGUID #endif #include #include #include #include #include #include #include #include #include #ifndef MF_E_TRANSFORM_NEED_MORE_INPUT #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) #endif #ifndef MFSTARTUP_NOSOCKET #define MFSTARTUP_NOSOCKET 0x1 #endif #ifdef _MSC_VER #pragma comment( lib, "ksuser" ) #pragma comment( lib, "mfplat.lib" ) #pragma comment( lib, "mfuuid.lib" ) #pragma comment( lib, "wmcodecdspuuid" ) #endif //============================================================================= #define SAFE_RELEASE( objectPtr )\ if ( objectPtr )\ {\ objectPtr->Release();\ objectPtr = NULL;\ } typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. // Therefore we must perform all necessary conversions to user buffers in order to satisfy these // requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to // provide intermediate storage for read / write synchronization. class WasapiBuffer { public: WasapiBuffer() : buffer_( NULL ), bufferSize_( 0 ), inIndex_( 0 ), outIndex_( 0 ) {} ~WasapiBuffer() { free( buffer_ ); } // sets the length of the internal ring buffer void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { free( buffer_ ); buffer_ = ( char* ) calloc( bufferSize, formatBytes ); bufferSize_ = bufferSize; inIndex_ = 0; outIndex_ = 0; } // attempt to push a buffer into the ring buffer at the current "in" index bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relOutIndex = outIndex_; unsigned int inIndexEnd = inIndex_ + bufferSize; if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { relOutIndex += bufferSize_; } // the "IN" index CAN BEGIN at the "OUT" index // the "IN" index CANNOT END at the "OUT" index if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } // copy buffer from external to internal int fromZeroSize = inIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromInSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); break; } // update "in" index inIndex_ += bufferSize; inIndex_ %= bufferSize_; return true; } // attempt to pull a buffer from the ring buffer from the current "out" index bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relInIndex = inIndex_; unsigned int outIndexEnd = outIndex_ + bufferSize; if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { relInIndex += bufferSize_; } // the "OUT" index CANNOT BEGIN at the "IN" index // the "OUT" index CAN END at the "IN" index if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } // copy buffer from internal to external int fromZeroSize = outIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromOutSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); break; } // update "out" index outIndex_ += bufferSize; outIndex_ %= bufferSize_; return true; } private: char* buffer_; unsigned int bufferSize_; unsigned int inIndex_; unsigned int outIndex_; }; //----------------------------------------------------------------------------- // In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate // between HW and the user. The WasapiResampler class is used to perform this conversion between // HwIn->UserIn and UserOut->HwOut during the stream callback loop. class WasapiResampler { public: WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, unsigned int inSampleRate, unsigned int outSampleRate ) : _bytesPerSample( bitsPerSample / 8 ) , _channelCount( channelCount ) , _sampleRatio( ( float ) outSampleRate / inSampleRate ) , _transformUnk( NULL ) , _transform( NULL ) , _mediaType( NULL ) , _inputMediaType( NULL ) , _outputMediaType( NULL ) #ifdef __IWMResamplerProps_FWD_DEFINED__ , _resamplerProps( NULL ) #endif { // 1. Initialization MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); // 2. Create Resampler Transform Object CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, ( void** ) &_transformUnk ); _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); #ifdef __IWMResamplerProps_FWD_DEFINED__ _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality #endif // 3. Specify input / output format MFCreateMediaType( &_mediaType ); _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); MFCreateMediaType( &_inputMediaType ); _mediaType->CopyAllItems( _inputMediaType ); _transform->SetInputType( 0, _inputMediaType, 0 ); MFCreateMediaType( &_outputMediaType ); _mediaType->CopyAllItems( _outputMediaType ); _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); _transform->SetOutputType( 0, _outputMediaType, 0 ); // 4. Send stream start messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); } ~WasapiResampler() { // 8. Send stream stop messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); // 9. Cleanup MFShutdown(); SAFE_RELEASE( _transformUnk ); SAFE_RELEASE( _transform ); SAFE_RELEASE( _mediaType ); SAFE_RELEASE( _inputMediaType ); SAFE_RELEASE( _outputMediaType ); #ifdef __IWMResamplerProps_FWD_DEFINED__ SAFE_RELEASE( _resamplerProps ); #endif } void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) { // no sample rate conversion required memcpy( outBuffer, inBuffer, inputBufferSize ); outSampleCount = inSampleCount; return; } unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); IMFMediaBuffer* rInBuffer; IMFSample* rInSample; BYTE* rInByteBuffer = NULL; // 5. Create Sample object from input data MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); memcpy( rInByteBuffer, inBuffer, inputBufferSize ); rInBuffer->Unlock(); rInByteBuffer = NULL; rInBuffer->SetCurrentLength( inputBufferSize ); MFCreateSample( &rInSample ); rInSample->AddBuffer( rInBuffer ); // 6. Pass input data to Resampler _transform->ProcessInput( 0, rInSample, 0 ); SAFE_RELEASE( rInBuffer ); SAFE_RELEASE( rInSample ); // 7. Perform sample rate conversion IMFMediaBuffer* rOutBuffer = NULL; BYTE* rOutByteBuffer = NULL; MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; DWORD rStatus; DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput // 7.1 Create Sample object for output data memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); MFCreateSample( &( rOutDataBuffer.pSample ) ); MFCreateMemoryBuffer( rBytes, &rOutBuffer ); rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); rOutDataBuffer.dwStreamID = 0; rOutDataBuffer.dwStatus = 0; rOutDataBuffer.pEvents = NULL; // 7.2 Get output data from Resampler if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) { outSampleCount = 0; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); return; } // 7.3 Write output data to outBuffer SAFE_RELEASE( rOutBuffer ); rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); rOutBuffer->GetCurrentLength( &rBytes ); rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); memcpy( outBuffer, rOutByteBuffer, rBytes ); rOutBuffer->Unlock(); rOutByteBuffer = NULL; outSampleCount = rBytes / _bytesPerSample / _channelCount; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); } private: unsigned int _bytesPerSample; unsigned int _channelCount; float _sampleRatio; IUnknown* _transformUnk; IMFTransform* _transform; IMFMediaType* _mediaType; IMFMediaType* _inputMediaType; IMFMediaType* _outputMediaType; #ifdef __IWMResamplerProps_FWD_DEFINED__ IWMResamplerProps* _resamplerProps; #endif }; //----------------------------------------------------------------------------- // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { IAudioClient* captureAudioClient; IAudioClient* renderAudioClient; IAudioCaptureClient* captureClient; IAudioRenderClient* renderClient; HANDLE captureEvent; HANDLE renderEvent; WasapiHandle() : captureAudioClient( NULL ), renderAudioClient( NULL ), captureClient( NULL ), renderClient( NULL ), captureEvent( NULL ), renderEvent( NULL ) {} }; //============================================================================= RtApiWasapi::RtApiWasapi() : coInitialized_( false ), deviceEnumerator_( NULL ) { // WASAPI can run either apartment or multi-threaded HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; // Instantiate device enumerator hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); // If this runs on an old Windows, it will fail. Ignore and proceed. if ( FAILED( hr ) ) deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- RtApiWasapi::~RtApiWasapi() { if ( stream_.state != STREAM_CLOSED ) closeStream(); SAFE_RELEASE( deviceEnumerator_ ); // If this object previously called CoInitialize() if ( coInitialized_ ) CoUninitialize(); } //============================================================================= unsigned int RtApiWasapi::getDeviceCount( void ) { unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; if ( !deviceEnumerator_ ) return 0; // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; goto Exit; } Exit: // release all references SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); if ( errorText_.empty() ) return captureDeviceCount + renderDeviceCount; error( RtAudioError::DRIVER_ERROR ); return 0; } //----------------------------------------------------------------------------- RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; std::string defaultDeviceName; bool isCaptureDevice = false; PROPVARIANT deviceNameProp; PROPVARIANT defaultDeviceNameProp; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; IMMDevice* defaultDevicePtr = NULL; IAudioClient* audioClient = NULL; IPropertyStore* devicePropStore = NULL; IPropertyStore* defaultDevicePropStore = NULL; WAVEFORMATEX* deviceFormat = NULL; WAVEFORMATEX* closestMatchFormat = NULL; // probed info.probed = false; // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; errorType = RtAudioError::INVALID_USE; goto Exit; } // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; goto Exit; } isCaptureDevice = true; } else { hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; goto Exit; } isCaptureDevice = false; } // get default device name if ( isCaptureDevice ) { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; goto Exit; } } else { hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; goto Exit; } } hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; goto Exit; } PropVariantInit( &defaultDeviceNameProp ); hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; goto Exit; } defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); // name hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; goto Exit; } PropVariantInit( &deviceNameProp ); hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; goto Exit; } info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); // is default if ( isCaptureDevice ) { info.isDefaultInput = info.name == defaultDeviceName; info.isDefaultOutput = false; } else { info.isDefaultInput = false; info.isDefaultOutput = info.name == defaultDeviceName; } // channel count hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; goto Exit; } hr = audioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; goto Exit; } if ( isCaptureDevice ) { info.inputChannels = deviceFormat->nChannels; info.outputChannels = 0; info.duplexChannels = 0; } else { info.inputChannels = 0; info.outputChannels = deviceFormat->nChannels; info.duplexChannels = 0; } // sample rates info.sampleRates.clear(); // allow support for all sample rates as we have a built-in sample rate converter for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { info.sampleRates.push_back( SAMPLE_RATES[i] ); } info.preferredSampleRate = deviceFormat->nSamplesPerSec; // native format info.nativeFormats = 0; if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) { if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_FLOAT32; } else if ( deviceFormat->wBitsPerSample == 64 ) { info.nativeFormats |= RTAUDIO_FLOAT64; } } else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) { if ( deviceFormat->wBitsPerSample == 8 ) { info.nativeFormats |= RTAUDIO_SINT8; } else if ( deviceFormat->wBitsPerSample == 16 ) { info.nativeFormats |= RTAUDIO_SINT16; } else if ( deviceFormat->wBitsPerSample == 24 ) { info.nativeFormats |= RTAUDIO_SINT24; } else if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_SINT32; } } // probed info.probed = true; Exit: // release all references PropVariantClear( &deviceNameProp ); PropVariantClear( &defaultDeviceNameProp ); SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); SAFE_RELEASE( defaultDevicePtr ); SAFE_RELEASE( audioClient ); SAFE_RELEASE( devicePropStore ); SAFE_RELEASE( defaultDevicePropStore ); CoTaskMemFree( deviceFormat ); CoTaskMemFree( closestMatchFormat ); if ( !errorText_.empty() ) error( errorType ); return info; } //----------------------------------------------------------------------------- unsigned int RtApiWasapi::getDefaultOutputDevice( void ) { for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { if ( getDeviceInfo( i ).isDefaultOutput ) { return i; } } return 0; } //----------------------------------------------------------------------------- unsigned int RtApiWasapi::getDefaultInputDevice( void ) { for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { if ( getDeviceInfo( i ).isDefaultInput ) { return i; } } return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiWasapi::closeStream: No open stream to close."; error( RtAudioError::WARNING ); return; } if ( stream_.state != STREAM_STOPPED ) stopStream(); // clean up stream memory SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); delete ( WasapiHandle* ) stream_.apiHandle; stream_.apiHandle = NULL; for ( int i = 0; i < 2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // update stream state stream_.state = STREAM_CLOSED; } //----------------------------------------------------------------------------- void RtApiWasapi::startStream( void ) { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiWasapi::startStream: The stream is already running."; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif // update stream state stream_.state = STREAM_RUNNING; // create WASAPI stream thread stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); if ( !stream_.callbackInfo.thread ) { errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; error( RtAudioError::THREAD_ERROR ); } else { SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); ResumeThread( ( void* ) stream_.callbackInfo.thread ); } } //----------------------------------------------------------------------------- void RtApiWasapi::stopStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; // wait until stream thread is stopped while( stream_.state != STREAM_STOPPED ) { Sleep( 1 ); } // Wait for the last buffer to play before stopping. Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- void RtApiWasapi::abortStream( void ) { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; error( RtAudioError::WARNING ); return; } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; // wait until stream thread is stopped while ( stream_.state != STREAM_STOPPED ) { Sleep( 1 ); } // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; error( RtAudioError::THREAD_ERROR ); return; } stream_.callbackInfo.thread = (ThreadHandle) NULL; } //----------------------------------------------------------------------------- bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) { bool methodResult = FAILURE; unsigned int captureDeviceCount = 0; unsigned int renderDeviceCount = 0; IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; IMMDevice* devicePtr = NULL; WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; // create API Handle if not already created if ( !stream_.apiHandle ) stream_.apiHandle = ( void* ) new WasapiHandle(); // Count capture devices errorText_.clear(); RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; goto Exit; } // validate device index if ( device >= captureDeviceCount + renderDeviceCount ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; goto Exit; } // if device index falls within capture devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { errorType = RtAudioError::INVALID_USE; errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; goto Exit; } // retrieve captureAudioClient from devicePtr IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // if device index falls within render devices and is configured for loopback if ( device < renderDeviceCount && mode == INPUT ) { // if renderAudioClient is not initialised, initialise it now IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( !renderAudioClient ) { probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); } // retrieve captureAudioClient from devicePtr IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // if device index falls within render devices and is configured for output if ( device < renderDeviceCount && mode == OUTPUT ) { // if renderAudioClient is already initialised, don't initialise it again IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( renderAudioClient ) { methodResult = SUCCESS; goto Exit; } hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // fill stream data if ( ( stream_.mode == OUTPUT && mode == INPUT ) || ( stream_.mode == INPUT && mode == OUTPUT ) ) { stream_.mode = DUPLEX; } else { stream_.mode = mode; } stream_.device[mode] = device; stream_.doByteSwap[mode] = false; stream_.sampleRate = sampleRate; stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); if ( !stream_.userBuffer[mode] ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; goto Exit; } if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) stream_.callbackInfo.priority = 15; else stream_.callbackInfo.priority = 0; ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode methodResult = SUCCESS; Exit: //clean up SAFE_RELEASE( captureDevices ); SAFE_RELEASE( renderDevices ); SAFE_RELEASE( devicePtr ); CoTaskMemFree( deviceFormat ); // if method failed, close the stream if ( methodResult == FAILURE ) closeStream(); if ( !errorText_.empty() ) error( errorType ); return methodResult; } //============================================================================= DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); return 0; } DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); return 0; } DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::wasapiThread() { // as this is a new thread, we must CoInitialize it CoInitialize( NULL ); HRESULT hr; IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; float captureSrRatio = 0.0f; float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; WasapiResampler* captureResampler = NULL; WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; unsigned long captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; // convBuffer is used to store converted buffers between WASAPI and the user char* convBuffer = NULL; unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; std::string errorText; RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } // start capture stream if applicable if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init captureResampler captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], captureFormat->nSamplesPerSec, stream_.sampleRate ); captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, captureFormat, NULL ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } // don't configure captureEvent if in loopback mode if ( !loopbackEnabled ) { // configure captureEvent to trigger on every available capture buffer captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } hr = captureAudioClient->SetEventHandle( captureEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; // reset the capture stream hr = captureAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; goto Exit; } // start the capture stream hr = captureAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; goto Exit; } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } // scale outBufferSize according to stream->user sample rate ratio unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init renderResampler renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], stream_.sampleRate, renderFormat->nSamplesPerSec ); renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, renderFormat, NULL ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } // configure renderEvent to trigger on every available render buffer renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; // reset the render stream hr = renderAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; goto Exit; } // start the render stream hr = renderAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; goto Exit; } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } // scale inBufferSize according to user->stream sample rate ratio unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); } // malloc buffer memory if ( stream_.mode == INPUT ) { using namespace std; // for ceilf convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } convBuffSize *= 2; // allow overflow for *SrRatio remainders convBuffer = ( char* ) calloc( convBuffSize, 1 ); stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } // stream process loop while ( stream_.state != STREAM_STOPPING ) { if ( !callbackPulled ) { // Callback Input // ============== // 1. Pull callback buffer from inputBuffer // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count // Convert callback buffer to user format if ( captureAudioClient ) { int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); if ( captureSrRatio != 1 ) { // account for remainders samplesToPull--; } convBufferSize = 0; while ( convBufferSize < stream_.bufferSize ) { // Pull callback buffer from inputBuffer callbackPulled = captureBuffer.pullBuffer( convBuffer, samplesToPull * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ); if ( !callbackPulled ) { break; } // Convert callback buffer to user sample rate unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); unsigned int convSamples = 0; captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, convBuffer, samplesToPull, convSamples ); convBufferSize += convSamples; samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples } if ( callbackPulled ) { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } else { // no further conversion, simple copy deviceBuffer to userBuffer memcpy( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); } } } else { // if there is no capture stream, set callbackPulled flag callbackPulled = true; } // Execute Callback // ================ // 1. Execute user callback method // 2. Handle return value from callback // if callback has not requested the stream to stop if ( callbackPulled && !callbackStopped ) { // Execute user callback method callbackResult = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, getStreamTime(), captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); // tick stream time RtApi::tickStreamTime(); // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } callbackStopped = true; } else if ( callbackResult == 2 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } callbackStopped = true; } } } // Callback Output // =============== // 1. Convert callback buffer to stream format // 2. Convert callback buffer to stream sample rate and channel count // 3. Push callback buffer into outputBuffer if ( renderAudioClient && callbackPulled ) { // if the last call to renderBuffer.PushBuffer() was successful if ( callbackPushed || convBufferSize == 0 ) { if ( stream_.doConvertBuffer[OUTPUT] ) { // Convert callback buffer to stream format convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); } else { // no further conversion, simple copy userBuffer to deviceBuffer memcpy( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); } // Convert callback buffer to stream sample rate renderResampler->Convert( convBuffer, stream_.deviceBuffer, stream_.bufferSize, convBufferSize ); } // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { // if there is no render stream, set callbackPushed flag callbackPushed = true; } // Stream Capture // ============== // 1. Get capture buffer from stream // 2. Push capture buffer into inputBuffer // 3. If 2. was successful: Release capture buffer if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); } // Get capture buffer from stream hr = captureClient->GetBuffer( &streamBuffer, &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } if ( bufferFrameCount != 0 ) { // Push capture buffer into inputBuffer if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ) ) { // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } // Stream Render // ============= // 1. Get render buffer from stream // 2. Pull next buffer from outputBuffer // 3. If 2. was successful: Fill render buffer with next buffer // Release render buffer if ( renderAudioClient ) { // if the callback output buffer was not pushed to renderBuffer, wait for next render event if ( callbackPulled && !callbackPushed ) { WaitForSingleObject( renderEvent, INFINITE ); } // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } bufferFrameCount -= numFramesPadding; if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } // Pull next buffer from outputBuffer // Fill render buffer with next buffer if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ) ) { // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { // unsetting the callbackPulled flag lets the stream know that // the audio device is ready for another callback output buffer. callbackPulled = false; } } Exit: // clean up CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); free ( convBuffer ); delete renderResampler; delete captureResampler; CoUninitialize(); // update stream state stream_.state = STREAM_STOPPED; if ( !errorText.empty() ) { errorText_ = errorText; error( errorType ); } } //******************** End of __WINDOWS_WASAPI__ *********************// #endif #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 // - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 #include #include #include #include #include #include #include #if defined(__MINGW32__) // missing from latest mingw winapi #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif #define MINIMUM_DEVICE_BUFFER_SIZE 32768 #ifdef _MSC_VER // if Microsoft Visual C++ #pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. #endif static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) { if ( pointer > bufferSize ) pointer -= bufferSize; if ( laterPointer < earlierPointer ) laterPointer += bufferSize; if ( pointer < earlierPointer ) pointer += bufferSize; return pointer >= earlierPointer && pointer < laterPointer; } // A structure to hold various information related to the DirectSound // API implementation. struct DsHandle { unsigned int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. void *id[2]; void *buffer[2]; bool xrun[2]; UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; DsHandle() :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } }; // Declarations for utility functions, callbacks, and structures // specific to the DirectSound implementation. static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR module, LPVOID lpContext ); static const char* getErrorString( int code ); static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id[2]; bool validId[2]; bool found; std::string name; DsDevice() : found(false) { validId[0] = false; validId[1] = false; } }; struct DsProbeData { bool isInput; std::vector* dsDevices; }; RtApiDs :: RtApiDs() { // Dsound will run both-threaded. If CoInitialize fails, then just // accept whatever the mainline chose for a threading model. coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; } RtApiDs :: ~RtApiDs() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); // balanced call. } // The DirectSound default output is always the first device. unsigned int RtApiDs :: getDefaultOutputDevice( void ) { return 0; } // The DirectSound default input is always the first input device, // which is the first capture device enumerated. unsigned int RtApiDs :: getDefaultInputDevice( void ) { return 0; } unsigned int RtApiDs :: getDeviceCount( void ) { // Set query flag for previously found devices to false, so that we // can check for any devices that have disappeared. for ( unsigned int i=0; i(dsDevices.size()); } RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; if ( dsDevices.size() == 0 ) { // Force a query of all devices getDeviceCount(); if ( dsDevices.size() == 0 ) { errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } } if ( device >= dsDevices.size() ) { errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } HRESULT result; if ( dsDevices[ device ].validId[0] == false ) goto probeInput; LPDIRECTSOUND output; DSCAPS outCaps; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto probeInput; } // Get output channel information. info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; // Get sample rate information. info.sampleRates.clear(); for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } // Get format information. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; output->Release(); if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; if ( dsDevices[ device ].validId[1] == false ) { info.name = dsDevices[ device ].name; info.probed = true; return info; } probeInput: LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get input channel information. info.inputChannels = inCaps.dwChannels; // Get sample rate and format information. std::vector rates; if ( inCaps.dwChannels >= 2 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); } } else if ( inCaps.dwChannels == 1 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); } } else info.inputChannels = 0; // technically, this would be an error input->Release(); if ( info.inputChannels == 0 ) return info; // Copy the supported rates to the info structure but avoid duplication. bool found; for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; if ( device == 0 ) info.isDefaultInput = true; // Copy name and return. info.name = dsDevices[ device ].name; info.probed = true; return info; } bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { if ( channels + firstChannel > 2 ) { errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; return FAILURE; } size_t nDevices = dsDevices.size(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; return FAILURE; } if ( mode == OUTPUT ) { if ( dsDevices[ device ].validId[0] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; errorText_ = errorStream_.str(); return FAILURE; } } else { // mode == INPUT if ( dsDevices[ device ].validId[1] == false ) { errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; errorText_ = errorStream_.str(); return FAILURE; } } // According to a note in PortAudio, using GetDesktopWindow() // instead of GetForegroundWindow() is supposed to avoid problems // that occur when the application's window is not the foreground // window. Also, if the application window closes before the // DirectSound buffer, DirectSound can crash. In the past, I had // problems when using GetDesktopWindow() but it seems fine now // (January 2010). I'll leave it commented here. // HWND hWnd = GetForegroundWindow(); HWND hWnd = GetDesktopWindow(); // Check the numberOfBuffers parameter and limit the lowest value to // two. This is a judgement call and a value of two is probably too // low for capture, but it should work for playback. int nBuffers = 0; if ( options ) nBuffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; if ( nBuffers < 2 ) nBuffers = 3; // Check the lower range of the user-specified buffer size and set // (arbitrarily) to a lower bound of 32. if ( *bufferSize < 32 ) *bufferSize = 32; // Create the wave format structure. The data format setting will // be determined later. WAVEFORMATEX waveFormat; ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = channels + firstChannel; waveFormat.nSamplesPerSec = (unsigned long) sampleRate; // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; HRESULT result; if ( mode == OUTPUT ) { LPDIRECTSOUND output; result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCAPS outCaps; outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; errorText_ = errorStream_.str(); return FAILURE; } // Check format information. Use 16-bit format unless not // supported or user requests 8-bit. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Even though we will write to the secondary buffer, we need to // access the primary buffer to set the correct output format // (since the default is 8-bit, 22 kHz!). Setup the DS primary // buffer description. DSBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // Obtain the primary buffer LPDIRECTSOUNDBUFFER buffer; result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Set the primary DS buffer sound format. result = buffer->SetFormat( &waveFormat ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Setup the secondary DS buffer description. ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCHARDWARE ); // Force hardware mixing bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Try to create the secondary DS buffer. If that doesn't work, // try to use software mixing. Otherwise, there's a problem. result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE ); // Force software mixing result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } } // Get the buffer size ... might be different from what we specified. DSBCAPS dsbcaps; dsbcaps.dwSize = sizeof( DSBCAPS ); result = buffer->GetCaps( &dsbcaps ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dsbcaps.dwBufferBytes; // Lock the DS buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) output; bhandle = (void *) buffer; } if ( mode == INPUT ) { LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( inCaps.dwChannels < channels + firstChannel ) { errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; return FAILURE; } // Check format information. Use 16-bit format unless user // requests 8-bit. DWORD deviceFormats; if ( channels + firstChannel == 2 ) { deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } else { // channel == 1 deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Setup the secondary DS buffer description. DSCBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); bufferDescription.dwFlags = 0; bufferDescription.dwReserved = 0; bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Create the capture buffer. LPDIRECTSOUNDCAPTUREBUFFER buffer; result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Get the buffer size ... might be different from what we specified. DSCBCAPS dscbcaps; dscbcaps.dwSize = sizeof( DSCBCAPS ); result = buffer->GetCaps( &dscbcaps ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dscbcaps.dwBufferBytes; // NOTE: We could have a problem here if this is a duplex stream // and the play and capture hardware buffer sizes are different // (I'm actually not sure if that is a problem or not). // Currently, we are not verifying that. // Lock the capture buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the buffer ZeroMemory( audioPtr, dataLen ); // Unlock the buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) input; bhandle = (void *) buffer; } // Set various stream parameters DsHandle *handle = 0; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.nUserChannels[mode] = channels; stream_.bufferSize = *bufferSize; stream_.channelOffset[mode] = firstChannel; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Set flag for buffer conversion stream_.doConvertBuffer[mode] = false; if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) stream_.doConvertBuffer[mode] = true; if (stream_.userFormat != stream_.deviceFormat[mode]) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate our DsHandle structures for the stream. if ( stream_.apiHandle == 0 ) { try { handle = new DsHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } else handle = (DsHandle *) stream_.apiHandle; handle->id[mode] = ohandle; handle->buffer[mode] = bhandle; handle->dsBufferSize[mode] = dsBufferSize; handle->dsPointerLeadTime[mode] = dsPointerLeadTime; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up an output stream. stream_.mode = DUPLEX; else stream_.mode = mode; stream_.nBuffers = nBuffers; stream_.sampleRate = sampleRate; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup the callback thread. if ( stream_.callbackInfo.isRunning == false ) { unsigned threadId; stream_.callbackInfo.isRunning = true; stream_.callbackInfo.object = (void *) this; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, &stream_.callbackInfo, 0, &threadId ); if ( stream_.callbackInfo.thread == 0 ) { errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; goto error; } // Boost DS thread priority SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); } return SUCCESS; error: if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) buffer->Release(); object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) buffer->Release(); object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } // Stop the callback thread. stream_.callbackInfo.isRunning = false; WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); CloseHandle( (HANDLE) stream_.callbackInfo.thread ); DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiDs :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. timeBeginPeriod( 1 ); buffersRolling = false; duplexPrerollBytes = 0; if ( stream_.mode == DUPLEX ) { // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); } HRESULT result = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; result = buffer->Start( DSCBSTART_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; unlock: if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); // Stop the buffer and clear memory LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start playing again, we must begin at beginning of buffer. handle->bufferPointer[0] = 0; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; audioPtr = NULL; dataLen = 0; stream_.state = STREAM_STOPPED; if ( stream_.mode != DUPLEX ) MUTEX_LOCK( &stream_.mutex ); result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start recording again, we must begin at beginning of buffer. handle->bufferPointer[1] = 0; } unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. MUTEX_UNLOCK( &stream_.mutex ); if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } DsHandle *handle = (DsHandle *) stream_.apiHandle; handle->drainCounter = 2; stopStream(); } void RtApiDs :: callbackEvent() { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; DsHandle *handle = (DsHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else stopStream(); return; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } HRESULT result; DWORD currentWritePointer, safeWritePointer; DWORD currentReadPointer, safeReadPointer; UINT nextWritePointer; LPVOID buffer1 = NULL; LPVOID buffer2 = NULL; DWORD bufferSize1 = 0; DWORD bufferSize2 = 0; char *buffer; long bufferBytes; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } if ( buffersRolling == false ) { if ( stream_.mode == DUPLEX ) { //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); // It takes a while for the devices to get rolling. As a result, // there's no guarantee that the capture and write device pointers // will move in lockstep. Wait here for both devices to start // rolling, and then set our buffer pointers accordingly. // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 // bytes later than the write buffer. // Stub: a serious risk of having a pre-emptive scheduling round // take place between the two GetCurrentPosition calls... but I'm // really not sure how to solve the problem. Temporarily boost to // Realtime priority, maybe; but I'm not sure what priority the // DirectSound service threads run at. We *should* be roughly // within a ms or so of correct. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; DWORD startSafeWritePointer, startSafeReadPointer; result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); } //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; handle->bufferPointer[1] = safeReadPointer; } else if ( stream_.mode == OUTPUT ) { // Set the proper nextWritePosition after initial startup. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; } buffersRolling = true; } if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( handle->drainCounter > 1 ) { // write zeros to the output stream bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); memset( stream_.userBuffer[0], 0, bufferBytes ); } // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; bufferBytes *= formatBytes( stream_.deviceFormat[0] ); } else { buffer = stream_.userBuffer[0]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); } // No byte swapping necessary in DirectSound implementation. // Ahhh ... windoze. 16-bit data is signed but 8-bit data is // unsigned. So, we need to convert our signed 8-bit data here to // unsigned. if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) for ( int i=0; idsBufferSize[0]; nextWritePointer = handle->bufferPointer[0]; DWORD endWrite, leadPointer; while ( true ) { // Find out where the read and "safe write" pointers are. result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // We will copy our output buffer into the region between // safeWritePointer and leadPointer. If leadPointer is not // beyond the next endWrite position, wait until it is. leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset endWrite = nextWritePointer + bufferBytes; // Check whether the entire write region is behind the play pointer. if ( leadPointer >= endWrite ) break; // If we are here, then we must wait until the leadPointer advances // beyond the end of our next write region. We use the // Sleep() function to suspend operation until that happens. double millis = ( endWrite - leadPointer ) * 1000.0; millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); } if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { // We've strayed into the forbidden zone ... resync the read pointer. handle->xrun[0] = true; nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; handle->bufferPointer[0] = nextWritePointer; endWrite = nextWritePointer + bufferBytes; } // Lock free space in the buffer result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } // Copy our buffer into the DS buffer CopyMemory( buffer1, buffer, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); // Update our buffer offset and unlock sound buffer dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; bufferBytes *= formatBytes( stream_.deviceFormat[1] ); } else { buffer = stream_.userBuffer[1]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; bufferBytes *= formatBytes( stream_.userFormat ); } LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; long nextReadPointer = handle->bufferPointer[1]; DWORD dsBufferSize = handle->dsBufferSize[1]; // Find out where the write and "safe read" pointers are. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset DWORD endRead = nextReadPointer + bufferBytes; // Handling depends on whether we are INPUT or DUPLEX. // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, // then a wait here will drag the write pointers into the forbidden zone. // // In DUPLEX mode, rather than wait, we will back off the read pointer until // it's in a safe position. This causes dropouts, but it seems to be the only // practical way to sync up the read and write pointers reliably, given the // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in DUPLEX mode, we will // provide a pre-roll period of 0.5 seconds in which we return // zeros from the read buffer while the pointers sync up. if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { // Pre-roll time over. Be more agressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; // Two cases: // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, // and perform fine adjustments later. // - small adjustments: back off by twice as much. if ( adjustment >= 2*bufferBytes ) nextReadPointer = safeReadPointer-2*bufferBytes; else nextReadPointer = safeReadPointer-bufferBytes-adjustment; if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } else { // In pre=roll time. Just do it. nextReadPointer = safeReadPointer - bufferBytes; while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } endRead = nextReadPointer + bufferBytes; } } else { // mode == INPUT while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); // Wake up and find out where we are now. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset } } // Lock free space in the buffer result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } if ( duplexPrerollBytes <= 0 ) { // Copy our buffer into the DS buffer CopyMemory( buffer, buffer1, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); } else { memset( buffer, 0, bufferSize1 ); if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); duplexPrerollBytes -= bufferSize1 + bufferSize2; } // Update our buffer offset and unlock sound buffer nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } handle->bufferPointer[1] = nextReadPointer; // No byte swapping necessary in DirectSound implementation. // If necessary, convert 8-bit data from unsigned to signed. if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) for ( int j=0; jobject; bool* isRunning = &info->isRunning; while ( *isRunning == true ) { object->callbackEvent(); } _endthreadex( 0 ); return 0; } static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR /*module*/, LPVOID lpContext ) { struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) validDevice = true; } object->Release(); } else { DSCAPS caps; LPDIRECTSOUND object; hr = DirectSoundCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) validDevice = true; } object->Release(); } // If good device, then save its name and guid. std::string name = convertCharPointerToStdString( description ); //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) if ( lpguid == NULL ) name = "Default Device"; if ( validDevice ) { for ( unsigned int i=0; i #include // A structure to hold various information related to the ALSA API // implementation. struct AlsaHandle { snd_pcm_t *handles[2]; bool synchronized; bool xrun[2]; pthread_cond_t runnable_cv; bool runnable; AlsaHandle() :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } }; static void *alsaCallbackHandler( void * ptr ); RtApiAlsa :: RtApiAlsa() { // Nothing to do here. } RtApiAlsa :: ~RtApiAlsa() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiAlsa :: getDeviceCount( void ) { unsigned nDevices = 0; int result, subdevice, card; char name[64]; snd_ctl_t *handle = 0; // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { handle = 0; errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( handle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; nDevices++; } nextcard: if ( handle ) snd_ctl_close( handle ); snd_card_next( &card ); } result = snd_ctl_open( &handle, "default", 0 ); if (result == 0) { nDevices++; snd_ctl_close( handle ); } return nDevices; } RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; unsigned nDevices = 0; int result, subdevice, card; char name[64]; snd_ctl_t *chandle = 0; // Count cards and devices card = -1; subdevice = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { chandle = 0; errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto nextcard; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); break; } if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); goto foundDevice; } nDevices++; } nextcard: if ( chandle ) snd_ctl_close( chandle ); snd_card_next( &card ); } result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); goto foundDevice; } nDevices++; } if ( nDevices == 0 ) { errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } foundDevice: // If a stream is already open, we cannot probe the stream devices. // Thus, use the saved results. if ( stream_.state != STREAM_CLOSED && ( stream_.device[0] == device || stream_.device[1] == device ) ) { snd_ctl_close( chandle ); if ( device >= devices_.size() ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; error( RtAudioError::WARNING ); return info; } return devices_[ device ]; } int openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; snd_pcm_info_t *pcminfo; snd_pcm_info_alloca( &pcminfo ); snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); // First try for playback unless default device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_info_set_stream( pcminfo, stream ); if ( subdevice != -1 ) { snd_pcm_info_set_device( pcminfo, subdevice ); snd_pcm_info_set_subdevice( pcminfo, 0 ); result = snd_ctl_pcm_info( chandle, pcminfo ); if ( result < 0 ) { // Device probably doesn't support playback. goto captureProbe; } } result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } // Get output channel information. unsigned int value; result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); // Now try for capture unless default device (with subdev = -1) if ( subdevice != -1 ) { result = snd_ctl_pcm_info( chandle, pcminfo ); snd_ctl_close( chandle ); if ( result < 0 ) { // Device probably doesn't support capture. if ( info.outputChannels == 0 ) return info; goto probeParameters; } } else snd_ctl_close( chandle ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } info.inputChannels = value; snd_pcm_close( phandle ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // ALSA doesn't provide default devices so we'll use the first available one. if ( device == 0 && info.outputChannels > 0 ) info.isDefaultOutput = true; if ( device == 0 && info.inputChannels > 0 ) info.isDefaultInput = true; probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in // the direction with the maximum number of channels, or playback if // they are equal. This might limit our sample rate options, but so // be it. if ( info.outputChannels >= info.inputChannels ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Test our discrete set of sample rate values. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } if ( info.sampleRates.size() == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported data formats ... we don't care about endian-ness just yet snd_pcm_format_t format; info.nativeFormats = 0; format = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT8; format = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT16; format = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT24; format = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT32; format = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT32; format = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT64; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Get the device name char *cardname; result = snd_card_get_name( card, &cardname ); if ( result >= 0 ) { sprintf( name, "hw:%s,%d", cardname, subdevice ); free( cardname ); } info.name = name; // That's all ... close the device and return snd_pcm_close( phandle ); info.probed = true; return info; } void RtApiAlsa :: saveDeviceInfo( void ) { devices_.clear(); unsigned int nDevices = getDeviceCount(); devices_.resize( nDevices ); for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT ) snprintf(name, sizeof(name), "%s", "default"); else { // Count cards and devices card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } subdevice = -1; while( 1 ) { result = snd_ctl_pcm_next_device( chandle, &subdevice ); if ( result < 0 ) break; if ( subdevice < 0 ) break; if ( nDevices == device ) { sprintf( name, "hw:%d,%d", card, subdevice ); snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } snd_ctl_close( chandle ); snd_card_next( &card ); } result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } snd_ctl_close( chandle ); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; return FAILURE; } } foundDevice: // The getDeviceInfo() function will not work for a device that is // already open. Thus, we'll probe the system before opening a // stream and save the results for use by getDeviceInfo(). if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once this->saveDeviceInfo(); snd_pcm_stream_t stream; if ( mode == OUTPUT ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_t *phandle; int openMode = SND_PCM_ASYNC; result = snd_pcm_open( &phandle, name, stream, openMode ); if ( result < 0 ) { if ( mode == OUTPUT ) errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; else errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; errorText_ = errorStream_.str(); return FAILURE; } // Fill the parameter structure. snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca( &hw_params ); result = snd_pcm_hw_params_any( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set access ... check user preference. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { stream_.userInterleaved = false; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); stream_.deviceInterleaved[mode] = true; } else stream_.deviceInterleaved[mode] = false; } else { stream_.userInterleaved = true; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); stream_.deviceInterleaved[mode] = false; } else stream_.deviceInterleaved[mode] = true; } if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; if ( format == RTAUDIO_SINT8 ) deviceFormat = SND_PCM_FORMAT_S8; else if ( format == RTAUDIO_SINT16 ) deviceFormat = SND_PCM_FORMAT_S16; else if ( format == RTAUDIO_SINT24 ) deviceFormat = SND_PCM_FORMAT_S24; else if ( format == RTAUDIO_SINT32 ) deviceFormat = SND_PCM_FORMAT_S32; else if ( format == RTAUDIO_FLOAT32 ) deviceFormat = SND_PCM_FORMAT_FLOAT; else if ( format == RTAUDIO_FLOAT64 ) deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { stream_.deviceFormat[mode] = format; goto setFormat; } // The user requested format is not natively supported by the device. deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; goto setFormat; } deviceFormat = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT8; goto setFormat; } // If we get here, no supported format was found. snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; setFormat: result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine whether byte-swaping is necessary. stream_.doByteSwap[mode] = false; if ( deviceFormat != SND_PCM_FORMAT_S8 ) { result = snd_pcm_format_cpu_endian( deviceFormat ); if ( result == 0 ) stream_.doByteSwap[mode] = true; else if (result < 0) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } } // Set the sample rate. result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine the number of channels for this device. We support a possible // minimum device channel number > than the value requested by the user. stream_.nUserChannels[mode] = channels; unsigned int value; result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); unsigned int deviceChannels = value; if ( result < 0 || deviceChannels < channels + firstChannel ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } deviceChannels = value; if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; stream_.nDeviceChannels[mode] = deviceChannels; // Set the device channels. result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Set the buffer (or period) size. int dir = 0; snd_pcm_uframes_t periodSize = *bufferSize; result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } *bufferSize = periodSize; // Set the buffer number, which in ALSA is referred to as the "period". unsigned int periods = 0; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; if ( periods < 2 ) periods = 4; // a fairly safe default value result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; // Install the hardware configuration result = snd_pcm_hw_params( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. snd_pcm_sw_params_t *sw_params = NULL; snd_pcm_sw_params_alloca( &sw_params ); snd_pcm_sw_params_current( phandle, sw_params ); snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); // The following two settings were suggested by Theo Veenker //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); // here are two options for a fix //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); snd_pcm_uframes_t val; snd_pcm_sw_params_get_boundary( sw_params, &val ); snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); result = snd_pcm_sw_params( phandle, sw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); snd_pcm_sw_params_dump( sw_params, out ); #endif // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the ApiHandle if necessary and then save. AlsaHandle *apiInfo = 0; if ( stream_.apiHandle == 0 ) { try { apiInfo = (AlsaHandle *) new AlsaHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; goto error; } if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) apiInfo; apiInfo->handles[0] = 0; apiInfo->handles[1] = 0; } else { apiInfo = (AlsaHandle *) stream_.apiHandle; } apiInfo->handles[mode] = phandle; phandle = 0; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.nBuffers = periods; stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; // Link the streams if possible. apiInfo->synchronized = false; if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; error( RtAudioError::WARNING ); } } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiAlsa::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } if ( phandle) snd_pcm_close( phandle ); for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[0] ); if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[1] ); } if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiAlsa :: startStream() { // This method calls snd_pcm_prepare if the device isn't already in that state. verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { state = snd_pcm_state( handle[0] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } stream_.state = STREAM_RUNNING; unlock: apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( apiInfo->synchronized ) result = snd_pcm_drop( handle[0] ); else result = snd_pcm_drain( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = snd_pcm_drop( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: callbackEvent() { AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !apiInfo->runnable ) pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; apiInfo->xrun[0] = false; } if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; apiInfo->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int channels; snd_pcm_t **handle; snd_pcm_sframes_t frames; RtAudioFormat format; handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; channels = stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; channels = stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[1] ) result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[1] = true; result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto tryOutput; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); // Check stream latency result = snd_pcm_delay( handle[1], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; } tryOutput: if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); channels = stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; channels = stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer(buffer, stream_.bufferSize * channels, format); // Write samples to device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[0] ) result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[0] = true; result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } else errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RtAudioError::WARNING ); goto unlock; } // Check stream latency result = snd_pcm_delay( handle[0], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { std::cerr << "RtAudio alsa: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_ALSA__ *********************// #endif #if defined(__LINUX_PULSE__) // Code written by Peter Meerwald, pmeerw@pmeerw.net // and Tristan Matthews. #include #include #include static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 96000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; pa_sample_format_t pa_format; }; static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; struct PulseAudioHandle { pa_simple *s_play; pa_simple *s_rec; pthread_t thread; pthread_cond_t runnable_cv; bool runnable; PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiPulse::getDeviceCount( void ) { return 1; } RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; info.probed = true; info.name = "PulseAudio"; info.outputChannels = 2; info.inputChannels = 2; info.duplexChannels = 2; info.isDefaultOutput = true; info.isDefaultInput = true; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) info.sampleRates.push_back( *sr ); info.preferredSampleRate = 48000; info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; return info; } static void *pulseaudio_callback( void * user ) { CallbackInfo *cbi = static_cast( user ); RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (cbi->doRealtime) { std::cerr << "RtAudio pulse: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning ) { pthread_testcancel(); context->callbackEvent(); } pthread_exit( NULL ); } void RtApiPulse::closeStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.callbackInfo.isRunning = false; if ( pah ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( pah->thread, 0 ); if ( pah->s_play ) { pa_simple_flush( pah->s_play, NULL ); pa_simple_free( pah->s_play ); } if ( pah->s_rec ) pa_simple_free( pah->s_rec ); pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } if ( stream_.userBuffer[0] ) { free( stream_.userBuffer[0] ); stream_.userBuffer[0] = 0; } if ( stream_.userBuffer[1] ) { free( stream_.userBuffer[1] ); stream_.userBuffer[1] = 0; } stream_.state = STREAM_CLOSED; stream_.mode = UNINITIALIZED; } void RtApiPulse::callbackEvent( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !pah->runnable ) pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " "this shouldn't happen!"; error( RtAudioError::WARNING ); return; } RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; if ( stream_.state != STREAM_RUNNING ) goto unlock; int pa_error; size_t bytes; if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[OUTPUT] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[OUTPUT] ); } else bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { if ( stream_.doConvertBuffer[INPUT] ) bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[INPUT] ); else bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } if ( stream_.doConvertBuffer[INPUT] ) { convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) stopStream(); } void RtApiPulse::startStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::startStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiPulse::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif stream_.state = STREAM_RUNNING; pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::stopStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { int pa_error; if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::stopStream: error draining output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } void RtApiPulse::abortStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; error( RtAudioError::INVALID_USE ); return; } if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah && pah->s_play ) { int pa_error; if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RtAudioError::SYSTEM_ERROR ); return; } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); } bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { PulseAudioHandle *pah = 0; unsigned long bufferBytes = 0; pa_sample_spec ss; if ( device != 0 ) return false; if ( mode != INPUT && mode != OUTPUT ) return false; if ( channels != 1 && channels != 2 ) { errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; return false; } ss.channels = channels; if ( firstChannel != 0 ) return false; bool sr_found = false; for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { if ( sampleRate == *sr ) { sr_found = true; stream_.sampleRate = sampleRate; ss.rate = sampleRate; break; } } if ( !sr_found ) { errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; return false; } bool sf_found = 0; for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { if ( format == sf->rtaudio_format ) { sf_found = true; stream_.userFormat = sf->rtaudio_format; stream_.deviceFormat[mode] = stream_.userFormat; ss.format = sf->pa_format; break; } } if ( !sf_found ) { // Use internal data format conversion. stream_.userFormat = format; stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; ss.format = PA_SAMPLE_FLOAT32LE; } // Set other stream parameters. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; stream_.nBuffers = 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.channelOffset[mode] = 0; std::string streamName = "RtAudio"; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; goto error; } stream_.bufferSize = *bufferSize; if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); if ( !stream_.apiHandle ) { PulseAudioHandle *pah = new PulseAudioHandle; if ( !pah ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; goto error; } stream_.apiHandle = pah; if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; goto error; } } pah = static_cast( stream_.apiHandle ); int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { case INPUT: pa_buffer_attr buffer_attr; buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; case OUTPUT: pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; default: goto error; } if ( stream_.mode == UNINITIALIZED ) stream_.mode = mode; else if ( stream_.mode == mode ) goto error; else stream_.mode = DUPLEX; if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; stream_.state = STREAM_STOPPED; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo); pthread_attr_destroy(&attr); if(result != 0) { // Failed. Try instead with default attributes. result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo); if(result != 0) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; goto error; } } } return SUCCESS; error: if ( pah && stream_.callbackInfo.isRunning ) { pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } //******************** End of __LINUX_PULSE__ *********************// #endif #if defined(__LINUX_OSS__) #include #include #include #include #include #include #include static void *ossCallbackHandler(void * ptr); // A structure to hold various information related to the OSS API // implementation. struct OssHandle { int id[2]; // device ids bool xrun[2]; bool triggered; pthread_cond_t runnable; OssHandle() :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiOss :: RtApiOss() { // Nothing to do here. } RtApiOss :: ~RtApiOss() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiOss :: getDeviceCount( void ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return 0; } oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return 0; } close( mixerfd ); return sysinfo.numaudios; } RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) { RtAudio::DeviceInfo info; info.probed = false; int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; error( RtAudioError::WARNING ); return info; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; error( RtAudioError::WARNING ); return info; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; error( RtAudioError::INVALID_USE ); return info; } if ( device >= nDevices ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; error( RtAudioError::INVALID_USE ); return info; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe channels if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; if ( ainfo.caps & PCM_CAP_DUPLEX ) { if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } // Probe data formats ... do for input unsigned long mask = ainfo.iformats; if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) info.nativeFormats |= RTAUDIO_SINT16; if ( mask & AFMT_S8 ) info.nativeFormats |= RTAUDIO_SINT8; if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) info.nativeFormats |= RTAUDIO_SINT32; #ifdef AFMT_FLOAT if ( mask & AFMT_FLOAT ) info.nativeFormats |= RTAUDIO_FLOAT32; #endif if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) info.nativeFormats |= RTAUDIO_SINT24; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); return info; } // Probe the supported sample rates. info.sampleRates.clear(); if ( ainfo.nrates ) { for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; break; } } } } else { // Check min and max rate values; for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); } else { info.probed = true; info.name = ainfo.name; } return info; } bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; return FAILURE; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; return FAILURE; } unsigned nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; return FAILURE; } if ( device >= nDevices ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; return FAILURE; } oss_audioinfo ainfo; ainfo.dev = device; result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); close( mixerfd ); if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); return FAILURE; } // Check if device supports input or output if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { if ( mode == OUTPUT ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; else errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; errorText_ = errorStream_.str(); return FAILURE; } int flags = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( mode == OUTPUT ) flags |= O_WRONLY; else { // mode == INPUT if (stream_.mode == OUTPUT && stream_.device[0] == device) { // We just set the same device for playback ... close and reopen for duplex (OSS only). close( handle->id[0] ); handle->id[0] = 0; if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; errorText_ = errorStream_.str(); return FAILURE; } // Check that the number previously set channels is the same. if ( stream_.nUserChannels[0] != channels ) { errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } flags |= O_RDWR; } else flags |= O_RDONLY; } // Set exclusive access if specified. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; // Try to open the device. int fd; fd = open( ainfo.devnode, flags, 0 ); if ( fd == -1 ) { if ( errno == EBUSY ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; else errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // For duplex operation, specifically set this mode (this doesn't seem to work). /* if ( flags | O_RDWR ) { result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); if ( result == -1) { errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } } */ // Check the device channel support. stream_.nUserChannels[mode] = channels; if ( ainfo.max_channels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; errorText_ = errorStream_.str(); return FAILURE; } // Set the number of channels. int deviceChannels = channels + firstChannel; result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nDeviceChannels[mode] = deviceChannels; // Get the data format mask int mask; result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; int deviceFormat = -1; stream_.doByteSwap[mode] = false; if ( format == RTAUDIO_SINT8 ) { if ( mask & AFMT_S8 ) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } else if ( format == RTAUDIO_SINT16 ) { if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT24 ) { if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT32 ) { if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } } if ( deviceFormat == -1 ) { // The user requested format is not natively supported by the device. if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S8) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } if ( stream_.deviceFormat[mode] == 0 ) { // This really shouldn't happen ... close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; } // Set the data format. int temp = deviceFormat; result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); if ( result == -1 || deviceFormat != temp ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Attempt to set the buffer size. According to OSS, the minimum // number of buffers is two. The supposed minimum buffer size is 16 // bytes, so that will be our lower bound. The argument to this // call is in the form 0xMMMMSSSS (hex), where the buffer size (in // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. // We'll check the actual value used near the end of the setup // procedure. int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; if ( ossBufferBytes < 16 ) ossBufferBytes = 16; int buffers = 0; if ( options ) buffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; if ( buffers < 2 ) buffers = 3; temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nBuffers = buffers; // Save buffer size (in sample frames). *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); stream_.bufferSize = *bufferSize; // Set the sample rate. int srate = sampleRate; result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Verify the sample rate setup worked. if ( abs( srate - (int)sampleRate ) > 100 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = sampleRate; if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { // We're doing duplex setup here. stream_.deviceFormat[0] = stream_.deviceFormat[1]; stream_.nDeviceChannels[0] = deviceChannels; } // Set interleaving parameters. stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the stream handles if necessary and then save. if ( stream_.apiHandle == 0 ) { try { handle = new OssHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; goto error; } if ( pthread_cond_init( &handle->runnable, NULL ) ) { errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else { handle = (OssHandle *) stream_.apiHandle; } handle->id[mode] = fd; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.device[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; if ( stream_.device[0] == device ) handle->id[0] = fd; } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority. The higher priority will only take affect if the // program is run as root or suid. pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiOss::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; error( RtAudioError::WARNING ); return; } OssHandle *handle = (OssHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) pthread_cond_signal( &handle->runnable ); MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); else ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); stream_.state = STREAM_STOPPED; } if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; } void RtApiOss :: startStream() { verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts // when fed samples. MUTEX_UNLOCK( &stream_.mutex ); OssHandle *handle = (OssHandle *) stream_.apiHandle; pthread_cond_signal( &handle->runnable ); } void RtApiOss :: stopStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Flush the output with zeros a few times. char *buffer; int samples; RtAudioFormat format; if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } memset( buffer, 0, samples * formatBytes(format) ); for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; error( RtAudioError::WARNING ); } } result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: abortStream() { verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; error( RtAudioError::WARNING ); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() { OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); pthread_cond_wait( &handle->runnable, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RtAudioError::WARNING ); return; } // Invoke user callback to get fresh output data. int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { this->abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int samples; RtAudioFormat format; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer( buffer, samples, format ); if ( stream_.mode == DUPLEX && handle->triggered == false ) { int trig = 0; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); result = write( handle->id[0], buffer, samples * formatBytes(format) ); trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); handle->triggered = true; } else // Write samples to device. result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an underrun, though there isn't a // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; error( RtAudioError::WARNING ); // Continue on to input section. } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; samples = stream_.bufferSize * stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device. result = read( handle->id[1], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an overrun, though there isn't a // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; error( RtAudioError::WARNING ); goto unlock; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, samples, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (info->doRealtime) { std::cerr << "RtAudio oss: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_OSS__ *********************// #endif // *************************************************** // // // Protected common (OS-independent) RtAudio methods. // // *************************************************** // // This method can be modified to control the behavior of error // message printing. void RtApi :: error( RtAudioError::Type type ) { errorStream_.str(""); // clear the ostringstream RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; if ( errorCallback ) { // abortStream() can generate new error messages. Ignore them. Just keep original one. if ( firstErrorOccurred_ ) return; firstErrorOccurred_ = true; const std::string errorMessage = errorText_; if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { stream_.callbackInfo.isRunning = false; // exit from the thread abortStream(); } errorCallback( type, errorMessage ); firstErrorOccurred_ = false; return; } if ( type == RtAudioError::WARNING && showWarnings_ == true ) std::cerr << '\n' << errorText_ << "\n\n"; else if ( type != RtAudioError::WARNING ) throw( RtAudioError( errorText_, type ) ); } void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; error( RtAudioError::INVALID_USE ); } } void RtApi :: clearStreamInfo() { stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; stream_.sampleRate = 0; stream_.bufferSize = 0; stream_.nBuffers = 0; stream_.userFormat = 0; stream_.userInterleaved = true; stream_.streamTime = 0.0; stream_.apiHandle = 0; stream_.deviceBuffer = 0; stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; stream_.callbackInfo.errorCallback = 0; for ( int i=0; i<2; i++ ) { stream_.device[i] = 11111; stream_.doConvertBuffer[i] = false; stream_.deviceInterleaved[i] = true; stream_.doByteSwap[i] = false; stream_.nUserChannels[i] = 0; stream_.nDeviceChannels[i] = 0; stream_.channelOffset[i] = 0; stream_.deviceFormat[i] = 0; stream_.latency[i] = 0; stream_.userBuffer[i] = 0; stream_.convertInfo[i].channels = 0; stream_.convertInfo[i].inJump = 0; stream_.convertInfo[i].outJump = 0; stream_.convertInfo[i].inFormat = 0; stream_.convertInfo[i].outFormat = 0; stream_.convertInfo[i].inOffset.clear(); stream_.convertInfo[i].outOffset.clear(); } } unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; else if ( format == RTAUDIO_SINT24 ) return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; error( RtAudioError::WARNING ); return 0; } void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) { if ( mode == INPUT ) { // convert device to user buffer stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; stream_.convertInfo[mode].outFormat = stream_.userFormat; } else { // convert user to device buffer stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; stream_.convertInfo[mode].inFormat = stream_.userFormat; stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; } if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; else stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; // Set up the interleave/deinterleave offsets. if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || ( mode == INPUT && stream_.userInterleaved ) ) { for ( int k=0; k 0 ) { if ( stream_.deviceInterleaved[mode] ) { if ( mode == OUTPUT ) { for ( int k=0; k> 8); //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 16) & 0x0000ffff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8) & 0x00ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT24) { Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 24) & 0x000000ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i>8) | (x<<8); } //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { char val; char *ptr; ptr = buffer; if ( format == RTAUDIO_SINT16 ) { for ( unsigned int i=0; i= 4 #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) #else #define RTAUDIO_DLL_PUBLIC #endif #endif #include #include #include #include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. Support for signed integers and floats. Audio data fed to/from an RtAudio stream is assumed to ALWAYS be in host byte order. The internal routines will automatically take care of any necessary byte-swapping between the host format and the soundcard. Thus, endian-ness is not a concern in the following format definitions. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. - \e RTAUDIO_SINT24: 24-bit signed integer. - \e RTAUDIO_SINT32: 32-bit signed integer. - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. */ typedef unsigned long RtAudioFormat; static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. /*! \typedef typedef unsigned long RtAudioStreamFlags; \brief RtAudio stream option flags. The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). - \e RTAUDIO_JACK_DONT_CONNECT: Do not automatically connect ports (JACK only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. If the RTAUDIO_JACK_DONT_CONNECT flag is set, RtAudio will not attempt to automatically connect the ports of the client to the audio device. */ typedef unsigned int RtAudioStreamFlags; static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). static const RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT = 0x20; // Do not automatically connect ports (JACK only). /*! \typedef typedef unsigned long RtAudioStreamStatus; \brief RtAudio stream status (over- or underflow) flags. Notification of a stream over- or underflow is indicated by a non-zero stream \c status argument in the RtAudioCallback function. The stream status can be one of the following two options, depending on whether the stream is open for output and/or input: - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. */ typedef unsigned int RtAudioStreamStatus; static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. //! RtAudio callback function prototype. /*! All RtAudio clients must create a function of type RtAudioCallback to read and/or write data from/to the audio stream. When the underlying audio system is ready for new input or output data, this function will be invoked. \param outputBuffer For output (or duplex) streams, the client should write \c nFrames of audio sample frames into this buffer. This argument should be recast to the datatype specified when the stream was opened. For input-only streams, this argument will be NULL. \param inputBuffer For input (or duplex) streams, this buffer will hold \c nFrames of input audio sample frames. This argument should be recast to the datatype specified when the stream was opened. For output-only streams, this argument will be NULL. \param nFrames The number of sample frames of input or output data in the buffers. The actual buffer size in bytes is dependent on the data type and number of channels in use. \param streamTime The number of seconds that have elapsed since the stream was started. \param status If non-zero, this argument indicates a data overflow or underflow condition for the stream. The particular condition can be determined by comparison with the RtAudioStreamStatus flags. \param userData A pointer to optional data provided by the client when opening the stream (default = NULL). \return To continue normal stream operation, the RtAudioCallback function should return a value of zero. To stop the stream and drain the output buffer, the function should return a value of one. To abort the stream immediately, the client should return a value of two. */ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData ); /************************************************************************/ /*! \class RtAudioError \brief Exception handling class for RtAudio. The RtAudioError class is quite simple but it does allow errors to be "caught" by RtAudioError::Type. See the RtAudio documentation to know which methods can throw an RtAudioError. */ /************************************************************************/ class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error { public: //! Defined RtAudioError types. enum Type { WARNING, /*!< A non-critical error. */ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ MEMORY_ERROR, /*!< An error occured during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ DRIVER_ERROR, /*!< A system driver error occured. */ SYSTEM_ERROR, /*!< A system error occured. */ THREAD_ERROR /*!< A thread error occured. */ }; //! The constructor. RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) : std::runtime_error(message), type_(type) {} //! Prints thrown error message to stderr. virtual void printMessage( void ) const { std::cerr << '\n' << what() << "\n\n"; } //! Returns the thrown error message type. virtual const Type& getType(void) const { return type_; } //! Returns the thrown error message string. virtual const std::string getMessage(void) const { return std::string(what()); } protected: Type type_; }; //! RtAudio error callback function prototype. /*! \param type Type of error. \param errorText Error description. */ typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); // **************************************************************** // // // RtAudio class declaration. // // RtAudio is a "controller" used to select an available audio i/o // interface. It presents a common API for the user to call but all // functionality is implemented by the class RtApi and its // subclasses. RtAudio creates an instance of an RtApi subclass // based on the user's API choice. If no choice is made, RtAudio // attempts to make a "logical" API selection. // // **************************************************************** // class RtApi; class RTAUDIO_DLL_PUBLIC RtAudio { public: //! Audio API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ LINUX_PULSE, /*!< The Linux PulseAudio API. */ LINUX_OSS, /*!< The Linux Open Sound System API. */ UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft DirectSound API. */ RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ NUM_APIS /*!< Number of values in this enum. */ }; //! The public device information structure for returning queried values. struct DeviceInfo { bool probed; /*!< true if the device capabilities were successfully probed. */ std::string name; /*!< Character string device identifier. */ unsigned int outputChannels; /*!< Maximum output channels supported by device. */ unsigned int inputChannels; /*!< Maximum input channels supported by device. */ unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ bool isDefaultOutput; /*!< true if this is the default output device. */ bool isDefaultInput; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ unsigned int preferredSampleRate; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ // Default constructor. DeviceInfo() :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} }; //! The structure for specifying input or ouput stream parameters. struct StreamParameters { unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ unsigned int nChannels; /*!< Number of channels. */ unsigned int firstChannel; /*!< First channel index on device (default = 0). */ // Default constructor. StreamParameters() : deviceId(0), nChannels(0), firstChannel(0) {} }; //! The structure for specifying stream options. /*! The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME flag is set. It defines the thread's realtime priority. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. The \c numberOfBuffers parameter can be used to control stream latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs only. A value of two is usually the smallest allowed. Larger numbers can potentially result in more robust stream performance, though likely at the cost of stream latency. The value set by the user is replaced during execution of the RtAudio::openStream() function by the value actually used by the system. The \c streamName parameter can be used to set the client name when using the Jack API. By default, the client name is set to RtApiJack. However, if you wish to create multiple instances of RtAudio with Jack, each instance must have a unique client name. */ struct StreamOptions { RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ unsigned int numberOfBuffers; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ // Default constructor. StreamOptions() : flags(0), numberOfBuffers(0), priority(0) {} }; //! A static function to determine the current RtAudio version. static std::string getVersion( void ); //! A static function to determine the available compiled audio APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ); //! Return the name of a specified compiled audio API. /*! This obtains a short lower-case name used for identification purposes. This value is guaranteed to remain identical across library versions. If the API is unknown, this function will return the empty string. */ static std::string getApiName( RtAudio::Api api ); //! Return the display name of a specified compiled audio API. /*! This obtains a long name used for display purposes. If the API is unknown, this function will return the empty string. */ static std::string getApiDisplayName( RtAudio::Api api ); //! Return the compiled audio API having the given name. /*! A case insensitive comparison will check the specified name against the list of compiled APIs, and return the one which matches. On failure, the function returns UNSPECIFIED. */ static RtAudio::Api getCompiledApiByName( const std::string &name ); //! The class constructor. /*! The constructor performs minor initialization tasks. An exception can be thrown if no API support is compiled. If no API argument is specified and multiple API support has been compiled, the default order of use is JACK, ALSA, OSS (Linux systems) and ASIO, DS (Windows systems). */ RtAudio( RtAudio::Api api=UNSPECIFIED ); //! The destructor. /*! If a stream is running or open, it will be stopped and closed automatically. */ ~RtAudio(); //! Returns the audio API specifier for the current instance of RtAudio. RtAudio::Api getCurrentApi( void ); //! A public function that queries for the number of audio devices available. /*! This function performs a system query of available devices each time it is called, thus supporting devices connected \e after instantiation. If a system error occurs during processing, a warning will be issued. */ unsigned int getDeviceCount( void ); //! Return an RtAudio::DeviceInfo structure for a specified device number. /*! Any device integer between 0 and getDeviceCount() - 1 is valid. If an invalid argument is provided, an RtAudioError (type = INVALID_USE) will be thrown. If a device is busy or otherwise unavailable, the structure member "probed" will have a value of "false" and all other members are undefined. If the specified device is the current default input or output device, the corresponding "isDefault" member will have a value of "true". */ RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); //! A function that returns the index of the default output device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultOutputDevice( void ); //! A function that returns the index of the default input device. /*! If the underlying audio API does not provide a "default device", or if no devices are available, the return value will be 0. Note that this is a valid device identifier and it is the client's responsibility to verify that a device is available before attempting to open a stream. */ unsigned int getDefaultInputDevice( void ); //! A public function for opening a stream with the specified parameters. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be opened with the specified parameters or an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if any invalid device ID or channel number parameters are specified. \param outputParameters Specifies output stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For input-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param inputParameters Specifies input stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For output-only streams, this argument should be NULL. The device ID is an index value between 0 and getDeviceCount() - 1. \param format An RtAudioFormat specifying the desired sample data format. \param sampleRate The desired sample rate (sample frames per second). \param *bufferFrames A pointer to a value indicating the desired internal buffer size in sample frames. The actual value used by the device is returned via the same pointer. A value of zero can be specified, in which case the lowest allowable value is determined. \param callback A client-defined function that will be invoked when input data is available and/or output data is needed. \param userData An optional pointer to data that can be accessed from within the callback function. \param options An optional pointer to a structure containing various global stream options, including a list of OR'ed RtAudioStreamFlags and a suggested number of stream buffers that can be used to control stream latency. More buffers typically result in more robust performance, though at a cost of greater latency. If a value of zero is specified, a system-specific median value is chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. \param errorCallback A client-defined function that will be invoked when an error has occured. */ void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! If a stream is not open, this function issues a warning and returns (no exception is thrown). */ void closeStream( void ); //! A function that starts a stream. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already running. */ void startStream( void ); //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void stopStream( void ); //! Stop a stream, discarding any samples remaining in the input/output queue. /*! An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs during processing. An RtAudioError (type = INVALID_USE) is thrown if a stream is not open. A warning is issued if the stream is already stopped. */ void abortStream( void ); //! Returns true if a stream is open and false if not. bool isStreamOpen( void ) const; //! Returns true if the stream is running and false if it is stopped or not open. bool isStreamRunning( void ) const; //! Returns the number of elapsed seconds since the stream was started. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ double getStreamTime( void ); //! Set the stream time to a time in seconds greater than or equal to 0.0. /*! If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ void setStreamTime( double time ); //! Returns the internal stream latency in sample frames. /*! The stream latency refers to delay in audio input and/or output caused by internal buffering by the audio system and/or hardware. For duplex streams, the returned value will represent the sum of the input and output latencies. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. If the API does not report latency, the return value will be zero. */ long getStreamLatency( void ); //! Returns actual sample rate in use by the stream. /*! On some systems, the sample rate used may be slightly different than that specified in the stream parameters. If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. */ unsigned int getStreamSampleRate( void ); //! Specify whether warning messages should be printed to stderr. void showWarnings( bool value = true ); protected: void openRtApi( RtAudio::Api api ); RtApi *rtapi_; }; // Operating system dependent thread functionality. #if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; #elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; #else // Setup for "dummy" behavior #define __RTAUDIO_DUMMY__ typedef int ThreadHandle; typedef int StreamMutex; #endif // This global structure type is used to pass callback information // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { void *object; // Used as a "this" pointer. ThreadHandle thread; void *callback; void *userData; void *errorCallback; void *apiInfo; // void pointer for API specific callback information bool isRunning; bool doRealtime; int priority; // Default constructor. CallbackInfo() :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false), priority(0) {} }; // **************************************************************** // // // RtApi class declaration. // // Subclasses of RtApi contain all API- and OS-specific code necessary // to fully implement the RtAudio API. // // Note that RtApi is an abstract base class and cannot be // explicitly instantiated. The class RtAudio will create an // instance of an RtApi subclass (RtApiOss, RtApiAlsa, // RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). // // **************************************************************** // #pragma pack(push, 1) class S24 { protected: unsigned char c3[3]; public: S24() {} S24& operator = ( const int& i ) { c3[0] = (i & 0x000000ff); c3[1] = (i & 0x0000ff00) >> 8; c3[2] = (i & 0x00ff0000) >> 16; return *this; } S24( const double& d ) { *this = (int) d; } S24( const float& f ) { *this = (int) f; } S24( const signed short& s ) { *this = (int) s; } S24( const char& c ) { *this = (int) c; } int asInt() { int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); if (i & 0x800000) i |= ~0xffffff; return i; } }; #pragma pack(pop) #if defined( HAVE_GETTIMEOFDAY ) #include #endif #include class RTAUDIO_DLL_PUBLIC RtApi { public: RtApi(); virtual ~RtApi(); virtual RtAudio::Api getCurrentApi( void ) = 0; virtual unsigned int getDeviceCount( void ) = 0; virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; virtual unsigned int getDefaultInputDevice( void ); virtual unsigned int getDefaultOutputDevice( void ); void openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options, RtAudioErrorCallback errorCallback ); virtual void closeStream( void ); virtual void startStream( void ) = 0; virtual void stopStream( void ) = 0; virtual void abortStream( void ) = 0; long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); virtual double getStreamTime( void ); virtual void setStreamTime( double time ); bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void showWarnings( bool value ) { showWarnings_ = value; } protected: static const unsigned int MAX_SAMPLE_RATES; static const unsigned int SAMPLE_RATES[]; enum { FAILURE, SUCCESS }; enum StreamState { STREAM_STOPPED, STREAM_STOPPING, STREAM_RUNNING, STREAM_CLOSED = -50 }; enum StreamMode { OUTPUT, INPUT, DUPLEX, UNINITIALIZED = -75 }; // A protected structure used for buffer conversion. struct ConvertInfo { int channels; int inJump, outJump; RtAudioFormat inFormat, outFormat; std::vector inOffset; std::vector outOffset; }; // A protected structure for audio streams. struct RtApiStream { unsigned int device[2]; // Playback and record, respectively. void *apiHandle; // void pointer for API specific stream handle information StreamMode mode; // OUTPUT, INPUT, or DUPLEX. StreamState state; // STOPPED, RUNNING, or CLOSED char *userBuffer[2]; // Playback and record, respectively. char *deviceBuffer; bool doConvertBuffer[2]; // Playback and record, respectively. bool userInterleaved; bool deviceInterleaved[2]; // Playback and record, respectively. bool doByteSwap[2]; // Playback and record, respectively. unsigned int sampleRate; unsigned int bufferSize; unsigned int nBuffers; unsigned int nUserChannels[2]; // Playback and record, respectively. unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. unsigned int channelOffset[2]; // Playback and record, respectively. unsigned long latency[2]; // Playback and record, respectively. RtAudioFormat userFormat; RtAudioFormat deviceFormat[2]; // Playback and record, respectively. StreamMutex mutex; CallbackInfo callbackInfo; ConvertInfo convertInfo[2]; double streamTime; // Number of elapsed seconds since the stream started. #if defined(HAVE_GETTIMEOFDAY) struct timeval lastTickTimestamp; #endif RtApiStream() :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } }; typedef S24 Int24; typedef signed short Int16; typedef signed int Int32; typedef float Float32; typedef double Float64; std::ostringstream errorStream_; std::string errorText_; bool showWarnings_; RtApiStream stream_; bool firstErrorOccurred_; /*! Protected, api-specific method that attempts to open a device with the given parameters. This function MUST be implemented by all subclasses. If an error is encountered during the probe, a "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); //! A protected function used to increment the stream time. void tickStreamTime( void ); //! Protected common method to clear an RtApiStream structure. void clearStreamInfo(); /*! Protected common method that throws an RtAudioError (type = INVALID_USE) if a stream is not open. */ void verifyStream( void ); //! Protected common error method to allow global control over error handling. void error( RtAudioError::Type type ); /*! Protected method used to perform format, channel number, and/or interleaving conversions between the user and device buffers. */ void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); //! Protected common method used to perform byte-swapping on buffers. void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); //! Protected common method that returns the number of bytes for a given format. unsigned int formatBytes( RtAudioFormat format ); //! Protected common method that sets up the parameters for buffer conversion. void setConvertInfo( StreamMode mode, unsigned int firstChannel ); }; // **************************************************************** // // // Inline RtAudio definitions. // // **************************************************************** // inline RtAudio::Api RtAudio :: getCurrentApi( void ) { return rtapi_->getCurrentApi(); } inline unsigned int RtAudio :: getDeviceCount( void ) { return rtapi_->getDeviceCount(); } inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } inline unsigned int RtAudio :: getDefaultInputDevice( void ) { return rtapi_->getDefaultInputDevice(); } inline unsigned int RtAudio :: getDefaultOutputDevice( void ) { return rtapi_->getDefaultOutputDevice(); } inline void RtAudio :: closeStream( void ) { return rtapi_->closeStream(); } inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } inline bool RtAudio :: isStreamOpen( void ) const { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } // RtApi Subclass prototypes. #if defined(__MACOSX_CORE__) #include class RtApiCore: public RtApi { public: RtApiCore(); ~RtApiCore(); RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); static const char* getErrorCode( OSStatus code ); }; #endif #if defined(__UNIX_JACK__) class RtApiJack: public RtApi { public: RtApiJack(); ~RtApiJack(); RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( unsigned long nframes ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); bool shouldAutoconnect_; }; #endif #if defined(__WINDOWS_ASIO__) class RtApiAsio: public RtApi { public: RtApiAsio(); ~RtApiAsio(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! bool callbackEvent( long bufferIndex ); private: std::vector devices_; void saveDeviceInfo( void ); bool coInitialized_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__WINDOWS_DS__) class RtApiDs: public RtApi { public: RtApiDs(); ~RtApiDs(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } unsigned int getDeviceCount( void ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: bool coInitialized_; bool buffersRolling; long duplexPrerollBytes; std::vector dsDevices; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__WINDOWS_WASAPI__) struct IMMDeviceEnumerator; class RtApiWasapi : public RtApi { public: RtApiWasapi(); virtual ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); unsigned int getDefaultOutputDevice( void ); unsigned int getDefaultInputDevice( void ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); private: bool coInitialized_; IMMDeviceEnumerator* deviceEnumerator_; bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ); static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); void wasapiThread(); }; #endif #if defined(__LINUX_ALSA__) class RtApiAlsa: public RtApi { public: RtApiAlsa(); ~RtApiAlsa(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: std::vector devices_; void saveDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__LINUX_PULSE__) class RtApiPulse: public RtApi { public: ~RtApiPulse(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: std::vector devices_; void saveDeviceInfo( void ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__LINUX_OSS__) class RtApiOss: public RtApi { public: RtApiOss(); ~RtApiOss(); RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } unsigned int getDeviceCount( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); void closeStream( void ); void startStream( void ); void stopStream( void ); void abortStream( void ); // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesireable results! void callbackEvent( void ); private: bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); }; #endif #if defined(__RTAUDIO_DUMMY__) class RtApiDummy: public RtApi { public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } unsigned int getDeviceCount( void ) { return 0; } RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } void closeStream( void ) {} void startStream( void ) {} void stopStream( void ) {} void abortStream( void ) {} private: bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) { return false; } }; #endif #endif // Indentation settings for Vim and Emacs // // Local Variables: // c-basic-offset: 2 // indent-tabs-mode: nil // End: // // vim: et sts=2 sw=2 BambooTracker-0.3.5/BambooTracker/stream/RtAudio/RtAudio.pri000066400000000000000000000015561362177441300237110ustar00rootroot00000000000000SOURCES += \ $$PWD/RtAudio.cpp HEADERS += \ $$PWD/RtAudio.h linux { contains(DEFINES, __LINUX_PULSE__) { # DEFINES += __LINUX_PULSE__ LIBS += -lpthread -lpulse-simple -lpulse } contains(DEFINES, __UNIX_JACK__) { # DEFINES += __UNIX_JACK__ LIBS += -ljack -lpthread } DEFINES += __LINUX_ALSA__ LIBS += -lasound -lpthread } win32 { greaterThan(QT_MAJOR_VERSION, 4):greaterThan(QT_MINOR_VERSION, 5) { DEFINES += __WINDOWS_WASAPI__ LIBS += -lole32 -lwinmm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid } DEFINES += __WINDOWS_DS__ LIBS += -lole32 -lwinmm -ldsound } macx { contains(DEFINES, __UNIX_JACK__) { # DEFINES += __UNIX_JACK__ LIBS += -ljack -lpthread } DEFINES += __MACOSX_CORE__ LIBS += -framework CoreAudio -framework CoreFoundation -lpthread } BambooTracker-0.3.5/BambooTracker/stream/audio_stream.cpp000066400000000000000000000047371362177441300234430ustar00rootroot00000000000000#include "audio_stream.hpp" #include const std::string AudioStream::AUDIO_OUT_CLIENT_NAME = "BambooTracker"; AudioStream::AudioStream(QObject *parent) : QObject(parent), rate_(0), intrRate_(0), intrCount_(0), intrCountRest_(0), gcb_(nullptr), gcbPtr_(nullptr), tuState_(-1), started_(false), quitNotify_(false), tickNotifier_([this]() { tickNotifierRun(); }) { } AudioStream::~AudioStream() { quitNotify_.store(true); tickNotifierSem_.release(); tickNotifier_.join(); } void AudioStream::setGenerateCallback(GenerateCallback* cb, void* cbPtr) { std::lock_guard lock(mutex_); gcb_ = cb; gcbPtr_ = cbPtr; } void AudioStream::setTickUpdateCallback(TickUpdateCallback* cb, void* cbPtr) { std::lock_guard lock(mutex_); tucb_ = cb; tucbPtr_ = cbPtr; } bool AudioStream::initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device) { Q_UNUSED(duration) Q_UNUSED(backend) Q_UNUSED(device) started_ = false; rate_ = rate; setInterruption(intrRate); return true; } void AudioStream::setInterruption(uint32_t intrRate) { std::lock_guard lock(mutex_); intrRate_ = intrRate; intrCount_ = rate_ / intrRate_; } void AudioStream::start() { std::lock_guard lock(mutex_); started_ = true; } void AudioStream::stop() { std::lock_guard lock(mutex_); started_ = false; } void AudioStream::generate(int16_t* container, uint32_t nSamples) { GenerateCallback* gcb = nullptr; void* gcbPtr = nullptr; TickUpdateCallback* tucb = nullptr; bool started = false; std::unique_lock lock(mutex_, std::try_to_lock); if (lock.owns_lock()) { gcb = gcb_; gcbPtr = gcbPtr_; tucb = tucb_; started = started_; } if (!gcb || !tucb || !started) { std::fill(container, container + (nSamples << 1), 0); return; } int16_t* destPtr = container; while (nSamples) { if (!intrCountRest_) { // Interruption intrCountRest_ = intrCount_; // Set counts to next interruption generateTick(); } size_t count = std::min(intrCountRest_, nSamples); nSamples -= count; intrCountRest_ -= count; gcb(destPtr, count, gcbPtr); destPtr += (count << 1); // Move head } } void AudioStream::generateTick() { tuState_.store(tucb_(tucbPtr_)); tickNotifierSem_.release(); } void AudioStream::tickNotifierRun() { while (true) { tickNotifierSem_.acquire(); if (quitNotify_.load()) return; emit streamInterrupted(tuState_.load()); } } BambooTracker-0.3.5/BambooTracker/stream/audio_stream.hpp000066400000000000000000000027771362177441300234520ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include class AudioStream : public QObject { Q_OBJECT public: explicit AudioStream(QObject* parent = nullptr); virtual ~AudioStream(); using GenerateCallback = void (int16_t*, size_t, void*); void setGenerateCallback(GenerateCallback* cb, void* cbPtr); using TickUpdateCallback = int (void*); void setTickUpdateCallback(TickUpdateCallback* cb, void* cbPtr); // duration: miliseconds virtual bool initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device); virtual void shutdown() = 0; virtual std::vector getAvailableDevices() const = 0; virtual std::vector getAvailableBackends() const = 0; virtual std::string getCurrentBackend() const = 0; void setInterruption(uint32_t inrtRate); virtual void start(); virtual void stop(); signals: void streamInterrupted(int state); protected: static const std::string AUDIO_OUT_CLIENT_NAME; void generate(int16_t* container, uint32_t nSamples); private: uint32_t rate_; uint32_t intrRate_; uint32_t intrCount_; uint32_t intrCountRest_; std::mutex mutex_; GenerateCallback* gcb_; void* gcbPtr_; TickUpdateCallback* tucb_; void* tucbPtr_; std::atomic_int tuState_; bool started_; std::atomic_bool quitNotify_; QSemaphore tickNotifierSem_; std::thread tickNotifier_; void generateTick(); void tickNotifierRun(); }; BambooTracker-0.3.5/BambooTracker/stream/audio_stream_rtaudio.cpp000066400000000000000000000060141362177441300251600ustar00rootroot00000000000000#include "audio_stream_rtaudio.hpp" #include #include #include "RtAudio/RtAudio.h" AudioStreamRtAudio::AudioStreamRtAudio(QObject* parent) : AudioStream(parent) { audio_.reset(new RtAudio); } AudioStreamRtAudio::~AudioStreamRtAudio() { } bool AudioStreamRtAudio::initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device) { shutdown(); setBackend(backend); AudioStream::initialize(rate, duration, intrRate, backend, device); RtAudio* audio = audio_.get(); const std::string deviceUtf8 = device.toStdString(); RtAudio::StreamParameters param; param.nChannels = 2; param.deviceId = ~0u; for (unsigned int i = 0, n = audio->getDeviceCount(); i < n && param.deviceId == ~0u; ++i) { RtAudio::DeviceInfo info = audio->getDeviceInfo(i); if (info.outputChannels >= 2 && info.name == deviceUtf8) param.deviceId = i; } if (param.deviceId == ~0u) param.deviceId = audio->getDefaultOutputDevice(); RtAudio::StreamOptions opts; opts.flags = RTAUDIO_SCHEDULE_REALTIME; opts.streamName = AUDIO_OUT_CLIENT_NAME; auto callback = +[](void* outputBuffer, void*, unsigned int nFrames, double, RtAudioStreamStatus, void* userData) -> int { auto stream = reinterpret_cast(userData); stream->generate(static_cast(outputBuffer), nFrames); return 0; }; unsigned int bufferSize = rate * duration / 1000; try { audio->openStream(¶m, nullptr, RTAUDIO_SINT16, rate, &bufferSize, callback, this, &opts); return true; } catch (...) { return false; } } void AudioStreamRtAudio::shutdown() { if (audio_->isStreamOpen()) audio_->closeStream(); } void AudioStreamRtAudio::setBackend(const QString& backend) { std::vector apis; RtAudio::getCompiledApi(apis); size_t i = 0; for (const auto& api : apis) { std::string name = RtAudio::getApiDisplayName(api); if (backend == QString::fromStdString(name)) { audio_.reset(new RtAudio(apis[i])); return; } ++i; } audio_.reset(new RtAudio); } std::vector AudioStreamRtAudio::getAvailableDevices() const { RtAudio* audio = audio_.get(); std::vector devices; for (unsigned int i = 0, n = audio->getDeviceCount(); i < n; ++i) { RtAudio::DeviceInfo info = audio->getDeviceInfo(i); if (info.outputChannels >= 2) devices.push_back(info.name); } return devices; } std::vector AudioStreamRtAudio::getAvailableBackends() const { std::vector apis; std::vector names; RtAudio::getCompiledApi(apis); for (const auto& api : apis) names.push_back(RtAudio::getApiDisplayName(api)); return names; } std::string AudioStreamRtAudio::getCurrentBackend() const { return RtAudio::getApiDisplayName(audio_->getCurrentApi()); } void AudioStreamRtAudio::start() { AudioStream::start(); if (audio_->isStreamOpen() && !audio_->isStreamRunning()) audio_->startStream(); } void AudioStreamRtAudio::stop() { AudioStream::stop(); if (audio_->isStreamOpen() && audio_->isStreamRunning()) audio_->stopStream(); } BambooTracker-0.3.5/BambooTracker/stream/audio_stream_rtaudio.hpp000066400000000000000000000012751362177441300251710ustar00rootroot00000000000000#pragma once #include "audio_stream.hpp" #include class RtAudio; class AudioStreamRtAudio final : public AudioStream { public: explicit AudioStreamRtAudio(QObject* parent = nullptr); ~AudioStreamRtAudio() override; bool initialize(uint32_t rate, uint32_t duration, uint32_t intrRate, const QString& backend, const QString& device) override; void shutdown() override; std::vector getAvailableDevices() const override; std::vector getAvailableBackends() const override; std::string getCurrentBackend() const override; void start() override; void stop() override; private: std::unique_ptr audio_; void setBackend(const QString& backend); }; BambooTracker-0.3.5/BambooTracker/stream/timer.cpp000066400000000000000000000010101362177441300220640ustar00rootroot00000000000000#include "timer.hpp" Timer::Timer() {} Timer::~Timer() { stop(); } void Timer::setFunction(std::function func) { func_ = func; } void Timer::setInterval(const int microsec) { time_.store(microsec); } void Timer::start() { isContinue_.store(true); thread_ = std::thread([&] { while (isContinue_.load()) { std::this_thread::sleep_for(std::chrono::microseconds(time_.load())); func_(); } }); } void Timer::stop() { if (isContinue_.load()) { isContinue_.store(false); thread_.join(); } } BambooTracker-0.3.5/BambooTracker/stream/timer.hpp000066400000000000000000000005641362177441300221060ustar00rootroot00000000000000#pragma once #include #include #include #include class Timer { public: Timer(); ~Timer(); void setFunction(std::function func); void setInterval(const int microsec); void start(); void stop(); private: std::atomic time_; std::function func_; std::thread thread_; std::atomic_bool isContinue_; }; BambooTracker-0.3.5/BambooTracker/tick_counter.cpp000066400000000000000000000047211362177441300221560ustar00rootroot00000000000000#include "tick_counter.hpp" TickCounter::TickCounter() : isPlaySong_(false), tempo_(150), // Dummy set tickRate_(60), // NTSC nextGroovePos_(-1), defStepSize_(6) // Dummy set { updateTickDIf(); } void TickCounter::setInterruptRate(uint32_t rate) { tickRate_ = static_cast(rate); updateTickDIf(); } void TickCounter::setTempo(int tempo) { tempo_ = tempo; updateTickDIf(); tickDifSum_ = 0; resetRest(); } int TickCounter::getTempo() const { return tempo_; } void TickCounter::setSpeed(int speed) { defStepSize_ = speed; updateTickDIf(); tickDifSum_ = 0; resetRest(); } int TickCounter::getSpeed() const { return defStepSize_; } void TickCounter::setGroove(std::vector seq) { grooves_ = seq; if (nextGroovePos_ != -1) nextGroovePos_ = 0; } void TickCounter::setGrooveTrigger(GrooveTrigger trigger) { switch (trigger) { case GrooveTrigger::ValidByGlobal: nextGroovePos_ = static_cast(grooves_.size()) - 1; resetRest(); break; case GrooveTrigger::ValidByLocal: nextGroovePos_ = 0; resetRest(); --restTickToNextStep_; // Count down by step head break; case GrooveTrigger::Invalid: nextGroovePos_ = -1; resetRest(); break; } } bool TickCounter::getGrooveEnabled() const { return (nextGroovePos_ != -1); } void TickCounter::setPlayState(bool isPlaySong) { isPlaySong_ = isPlaySong; } /// Reuturn: /// -1: not tick or step /// 0: head of step /// 0<: rest tick count to next step int TickCounter::countUp() { if (isPlaySong_) { int ret = restTickToNextStep_; if (!restTickToNextStep_) { // When head of step, calculate real step size resetRest(); } --restTickToNextStep_; // Count down to next step return ret; } else { return -1; } } void TickCounter::updateTickDIf() { float strictTicksPerStepByBpm = 10.0f * tickRate_ * defStepSize_ / (tempo_ << 2); tickDif_ = strictTicksPerStepByBpm - static_cast(defStepSize_); } void TickCounter::resetCount() { restTickToNextStep_ = 0; tickDifSum_ = 0; } void TickCounter::resetRest() { if (nextGroovePos_ == -1) { tickDifSum_ += tickDif_; int castedTickDifSum = static_cast(tickDifSum_); restTickToNextStep_ = defStepSize_ + castedTickDifSum; if (restTickToNextStep_ < 1) restTickToNextStep_ = 1; // Prevent wait count changing to 0 (freeze) tickDifSum_ -= castedTickDifSum; } else { restTickToNextStep_ = grooves_.at(static_cast(nextGroovePos_)); nextGroovePos_ = (nextGroovePos_ + 1) % static_cast(grooves_.size()); } } BambooTracker-0.3.5/BambooTracker/tick_counter.hpp000066400000000000000000000013611362177441300221600ustar00rootroot00000000000000#pragma once #include #include #include enum class GrooveTrigger { ValidByGlobal, ValidByLocal, Invalid }; class TickCounter { public: TickCounter(); void setInterruptRate(uint32_t rate); void setTempo(int tempo); int getTempo() const; void setSpeed(int speed); int getSpeed() const; void setGroove(std::vector seq); void setGrooveTrigger(GrooveTrigger trigger); bool getGrooveEnabled() const; void setPlayState(bool isPlaySong); int countUp(); void resetCount(); private: bool isPlaySong_; int tempo_; int tickRate_; std::vector grooves_; int nextGroovePos_; int defStepSize_; int restTickToNextStep_; float tickDif_; float tickDifSum_; void updateTickDIf(); void resetRest(); }; BambooTracker-0.3.5/BambooTracker/version.hpp000066400000000000000000000053111362177441300211530ustar00rootroot00000000000000#pragma once #include #include #include "misc.hpp" class Version { public: static uint32_t ofApplicationInBCD(); static std::string ofApplicationInString(); static uint32_t ofModuleFileInBCD(); static std::string ofModuleFileInString(); static uint32_t ofInstrumentFileInBCD(); static std::string ofInstrumentFileInString(); static uint32_t ofBankFileInBCD(); static std::string ofBankFileInString(); static uint32_t toBCD(unsigned int major, unsigned int minor, unsigned int revision); static std::string toString(unsigned int major, unsigned int minor, unsigned int revision); private: // Application version static constexpr unsigned int appMajor = 0; static constexpr unsigned int appMinor = 3; static constexpr unsigned int appRevision = 5; // Module file version static constexpr unsigned int modFileMajor = 1; static constexpr unsigned int modFileMinor = 3; static constexpr unsigned int modFileRevision = 2; // Instrument file version static constexpr unsigned int instFileMajor = 1; static constexpr unsigned int instFileMinor = 2; static constexpr unsigned int instFileRevision = 3; // Bank file version static constexpr unsigned int bankFileMajor = 1; static constexpr unsigned int bankFileMinor = 0; static constexpr unsigned int bankFileRevision = 2; Version() {} }; inline uint32_t Version::ofApplicationInBCD() { return toBCD(appMajor, appMinor, appRevision); } inline std::string Version::ofApplicationInString() { return toString(appMajor, appMinor, appRevision); } inline uint32_t Version::ofModuleFileInBCD() { return toBCD(modFileMajor, modFileMinor, modFileRevision); } inline std::string Version::ofModuleFileInString() { return toString(modFileMajor, modFileMinor, modFileRevision); } inline uint32_t Version::ofInstrumentFileInBCD() { return toBCD(instFileMajor, instFileMinor, instFileRevision); } inline std::string Version::ofInstrumentFileInString() { return toString(instFileMajor, instFileMinor, instFileRevision); } inline uint32_t Version::ofBankFileInBCD() { return toBCD(bankFileMajor, bankFileMinor, bankFileRevision); } inline std::string Version::ofBankFileInString() { return toString(bankFileMajor, bankFileMinor, bankFileRevision); } inline uint32_t Version::toBCD(unsigned int major, unsigned int minor, unsigned int revision) { uint32_t maj = uitobcd(static_cast(major)); uint32_t min = uitobcd(static_cast(minor)); uint32_t rev = uitobcd(static_cast(revision)); return (maj << 16) + (min << 8) + rev; } inline std::string Version::toString(unsigned int major, unsigned int minor, unsigned int revision) { return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); } BambooTracker-0.3.5/CHANGELOG.md000066400000000000000000000760431362177441300160650ustar00rootroot00000000000000# Changelog ## Unreleased ## v0.3.5 (2020-02-15) ### Added - Support C86CTL ### Changed - Update SCCI library ### Fixed - Fix AutoEnv and square mask pitch calculation - [#181] - Fix crash when changing length of instrument sequence during jamming (thanks [@papiezak]) - Fix SCCI crash on launching in Windows XP (thanks [@ponzu0147]) - Fix SSG arpeggio effect crashing on setting `0000` without a instrument [@ponzu0147]: https://twitter.com/ponzu0147 [#181]: https://github.com/rerrahkr/BambooTracker/issues/181 ## v0.3.4 (2020-01-29) ### Added - [#179] - Jump to previous/next order or pattern by forward/backward mouse button (thanks [@Midi-In]) - [#178] - View-based horizontal scroll in the order list and the pattern editor (thanks [@Midi-In]) - 1 new sample instrument bank ### Changed - Change layout of the main window - Modify some words in the tracker - [#175] - Sound B7 when do key-on higher keys by keyboard (thanks [@papiezak]) - [#176] - Enable to show pattern step numbers in decimal (thanks [@Midi-In]) - Sound editing instrument in jamming - Keep step position of the pattern cursor in changing the current order - Unlock FM AR parameter when using SSG-EG - Change default emulation core to Nuked OPN-Mod - Update translation: ja ### Fixed - [#172], [#173] - Check MIDI in api (thanks [@OPNA2608], [@jpcima], [@papiezak]) - Fix drawing of pattern editor and order list - Fix jamming ignorance in FM & SSG editor using key layouts except QWERTY - [#180] - Fix crash at startup (thanks [@papiezak], [@Ikalou]) [@Midi-In]: https://github.com/Midi-In [@Ikalou]: https://github.com/Ikalou [#172]: https://github.com/rerrahkr/BambooTracker/issues/172 [#173]: https://github.com/rerrahkr/BambooTracker/pull/173 [#175]: https://github.com/rerrahkr/BambooTracker/issues/175 [#176]: https://github.com/rerrahkr/BambooTracker/issues/176 [#179]: https://github.com/rerrahkr/BambooTracker/issues/179 [#178]: https://github.com/rerrahkr/BambooTracker/issues/178 [#180]: https://github.com/rerrahkr/BambooTracker/issues/180 ## v0.3.3 (2019-12-17) ### Fixed - [#170] - Fix selected instrument indexing in bank export (thanks [@Ravancloak], [@ImATrackMan]) - Update backup if it already exists - [#170] - Copy command sequence type in deep clone instrument (thanks [@Ravancloak], [@ImATrackMan], [@galap-1]) - [#171] - Fix drawing error of the pattern editor when minimizing and showing the window during playback (thanks [@bryc]) [@Ravancloak]: https://github.com/Ravancloak [#170]: https://github.com/rerrahkr/BambooTracker/issues/170 [#171]: https://github.com/rerrahkr/BambooTracker/issues/171 ## v0.3.2 (2019-12-13) ### Changed - Change drawing method of FM envelope in FM instrument editor - Use Qt for file I/O - Modify FM envelope graph in FM instrument editor ### Fixed - Fix incorrect text painting in the pattern editor - [#168] - Fix pattern rendering glitches (thanks [@OPNA2608], [@YuzuMSX]) - [#169] - Fix the order list corruption after changing font (thanks [@jimbo1qaz]) - Fix pattern jump calculation - Change colors of the instrument list when appearance settings are changed - Fix text position in row of the order list and the pattern editor [@YuzuMSX]: https://github.com/YuzuMSX [#168]: https://github.com/rerrahkr/BambooTracker/issues/168 [#169]: https://github.com/rerrahkr/BambooTracker/issues/169 ## v0.3.1 (2019-11-30) ### Added - [#132] - 3 new pattern effects - `0Xxx`, `0Yxx`: register address set - `0Zxx`: register value set - `B0xx`: FM brightness ([#118]; thanks [@jpcima]) - Add 1 sample module (thanks [@maakmusic]) - Add selection whether auto-set current instrument upon entering note - Add color scheme settings - [#162] - Add order edit icons to the menu bar (thanks [@pedipanol]) - [#150] - Add the optimizing option to remove duplicate instruments (thanks [@papiezak]) - Add the icon of renaming instrument in the instrument list ### Changed - Change width of items in status bar - [#161] - Reduce drawing cost of the pattern editor and the order list (thanks [@papiezak]) - Change background color of odd columns in the instrument sequence editor - Improve SSG tone/noise editor - Change MML text in FM/SSG arpeggio editor from numbers to notes - [#162] - Allow order list edit in jam mode (thanks [@pedipanol]) - [#162] - Reverse the input order of the order numbers (thanks [@pedipanol]) - Update translation: ja ### Fixed - [#161] - Fix drawing error after loading song (thanks [@papiezak]) - Fix groove start position by `0Oxx` (thanks [@maakmusic]) - Fix some drawing errors in pattern editor and order list - Fix corruption after changing song type - [#163] - Fix the bug that the item is hidden in the instrument list (thanks [@galap-1]) - [#165] - Fix directive about std::hash with enum (thanks [@jpcima]) - [#166] - Fix uninitialized variables (thanks [@OPNA2608]) - [#167] - Fix the instrument list to disable the icons when it is empty (thanks [@OPNA2608]) [#161]: https://github.com/rerrahkr/BambooTracker/issues/161 [#162]: https://github.com/rerrahkr/BambooTracker/issues/162 [#163]: https://github.com/rerrahkr/BambooTracker/issues/163 [#150]: https://github.com/rerrahkr/BambooTracker/issues/150 [#118]: https://github.com/rerrahkr/BambooTracker/issues/118 [#165]: https://github.com/rerrahkr/BambooTracker/issues/165 [#166]: https://github.com/rerrahkr/BambooTracker/issues/166 [#167]: https://github.com/rerrahkr/BambooTracker/issues/167 ## v0.3.0 (2019-10-25) ### Added - [#96] - macOS support (thanks [@OPNA2608], [@jpcima] and others) - [#132] - 11 new pattern effects - `0Hxy`: SSG auto envelope ([#3]; thanks [@marysiamzawka]) - `0Ixx`, `0Jxx`: SSG hardware envelope period - `0Vxx`: SSG tone/noise mix (thanks [@pedipanol]) - `0Wxx`: SSG noise pitch (thanks [@pedipanol]) - `Axyy`: FM AR control - `Dxyy`: FM DR control (thanks [@papiezak]) - `FBxx`: FM FB control - `MLxy`: FM ML control - `RRxy`: FM RR control - `Txyy`: FM TL control - [#72] - Add module mixer settings (thanks [@ImATrackMan]) - Add sample rate selection in wav export - Add shortcuts of expanding/shrinking effect column - [#145] - Add configuration of font family and size of the pattern editor and the order list (thanks [@jimbo1qaz], [@papiezak]) ### Changed - [#152] - Use RtAudio for sound processing ([#96]; thanks [@jpcima], [@OPNA2608] and others) - [#96], [#152], [#158] - Reduce drawing cost of pattern editor and order list (thanks [@OPNA2608], [@papiezak]) - Disable slider operation during playback - [#156] - Change key-on/off of multiple drum instruments to write to register at once (thanks [@ImATrackMan], [@ValleyBell]) - Increase the maximum value of the master mixer to 200% - Change 3 shortcuts - F2: Focus on pattern editor - F3: Focus on order list - F4: Focus on instrument list ### Fixed - Fix to draw the playing cursor when follow mode is turned off - [#152] - Prevent real step size setting to 0 (thanks [@OPNA2608]) - [#153] - Fix hardware envelope to set data in sequence (thanks [@papiezak]) - [#154] - Fix position initialization when opening module (thanks [@papiezak]) - Fix corruption after `0Bxx`, `0Cxx` and `0Dxx` are set the position before current playback row - Fix incorrect square-mask pitch in square-masked saw and inversed saw - Reset character entry position in pattern editor and order list after undoing - Fix effect description in status bar after entering the 2nd character - Reset playback position when it is out of song range by editing pattern during playback - Restore FM RR parameters in key-on without instrument set after envelope reset - [#159] - Fix corruption happened by toggling track during playback (thanks [@papiezak]) [#152]: https://github.com/rerrahkr/BambooTracker/pull/152 [#153]: https://github.com/rerrahkr/BambooTracker/issues/153 [#154]: https://github.com/rerrahkr/BambooTracker/issues/154 [#156]: https://github.com/rerrahkr/BambooTracker/issues/156 [#132]: https://github.com/rerrahkr/BambooTracker/issues/132 [#72]: https://github.com/rerrahkr/BambooTracker/issues/72 [#158]: https://github.com/rerrahkr/BambooTracker/issues/158 [#159]: https://github.com/rerrahkr/BambooTracker/issues/159 [#145]: https://github.com/rerrahkr/BambooTracker/issues/145 [@ValleyBell]: https://github.com/ValleyBell ## v0.2.4 (2019-09-17) ### Changed - Finish value entry in the cell of pattern editor and order list when the cursor is moved - Update translation: ja ### Fixed - [#147], [#148] - Fix some typos (thanks [@alexmyczko]) - Ignore instrument addition when the instrument list has filled (thanks [@maakmusic]) - [#149] - Fix incorrect key off controls before key on (thanks [@CommodoreKulor]) - Fix size of track companding button in the pattern editor - [#137] - Fix redo behavior of effect ID entry when only 1 character is enterd (thanks [@SMB7]) - [#151] - Fix some code (thanks [@jimbo1qaz]) - Reset cursor position after loading module, song and module properties [@CommodoreKulor]: https://github.com/CommodoreKulor [#147]: https://github.com/rerrahkr/BambooTracker/pull/147 [#148]: https://github.com/rerrahkr/BambooTracker/pull/148 [#149]: https://github.com/rerrahkr/BambooTracker/issues/149 [#151]: https://github.com/rerrahkr/BambooTracker/issues/151 ## v0.2.3 (2019-08-31) ### Added - Add the selection of effect value initialization upon entering effect id - [#136] - Add instrument bank (thanks [@papiezak], [@jpcima]) - Keep selected file filter in instrument and bank import - Load instrument and bank by drag and drop - Add sample instruments (thanks [@papiezak]) ### Fixed - [#143] - Fix the corruption of note delay effect without note on (thanks [@ImATrackMan]) - [#146] - Fix the replay corruption after song-end effect (thanks [@papiezak]) [#143]: https://github.com/rerrahkr/BambooTracker/issues/143 [#136]: https://github.com/rerrahkr/BambooTracker/issues/136 [#146]: https://github.com/rerrahkr/BambooTracker/issues/146 ## v0.2.2 (2019-06-25) ### Added - [#141] - Add bank import and icons to instrument context menu (thanks [@jpcima]) - [#142] - Add search box for instrument selection dialog (thanks [@jpcima]) ### Changed - [#139] - Update translation: fr ([#122]; thanks [@jpcima], [@trebmuh]) ### Fixed - Fix SSG hardware envelope to reset on note on - Fix that Drum master volume effect is not available - [#137] - Fix corruption that occurred when using volume slide in FM3ch expansion (thanks [@SMB7]) - [#140] - Fix incorrect DT importing from DMP (thanks [@papiezak], [@jpcima], [@OPNA2608]) - [#138] - Fix that panning is reversed by insufficient stream buffer initialization (thanks [@jpcima]) - Fix the sequence start position when declaring sequence-type effect after key on - Fix note slide effect to enable to execute when speed is 0 - Fix default save and export name when the name of module file contains multiple dots [#137]: https://github.com/rerrahkr/BambooTracker/issues/137 [#140]: https://github.com/rerrahkr/BambooTracker/issues/140 [#139]: https://github.com/rerrahkr/BambooTracker/pull/139 [#141]: https://github.com/rerrahkr/BambooTracker/pull/141 [#138]: https://github.com/rerrahkr/BambooTracker/issues/138 [#142]: https://github.com/rerrahkr/BambooTracker/pull/142 [@SMB7]: https://github.com/SMB7 ## v0.2.1 (2019-06-16) ### Added - [#127] - Add effect display width to module (thanks [@ImATrackMan]) - Add 1 sample module (thanks [Dippy]) ### Changed - [#123], [#125] - Update translation: fr ([#122]; thanks [@trebmuh], [@jpcima]) - [#129] - Set the maximum count of FM channel to 6 in jamming during FX 3ch expanded mode (thanks [@galap-1]) - [#134] - Change delay effects to affect over step except note delay (thanks [@scarletbullgon]) ### Fixed - [#124] - Automatic section size for table column ([#122]; thanks [@jpcima], [@trebmuh]) - [#96] - Clean audio buffer initially (thanks [@OPNA2608]) - [#126] - Reset scrollbar positions in order list when loading song (thanks [@N-SPC700], [@OPNA2608]) - [#127] - Fix stream to change its interrupt rate when a module open (thanks [@ImATrackMan]) - [#128] - Fix that FM envelope paste was not worked (thanks [@papiezak]) - [#129] - Fix polyphonic jam-mode to accept unison when using MIDI keyboard (thanks [@galap-1]) - [#133] - Use Unicode paths ([#130]; thanks [@jpcima] and others) - [#135] - Delete unit data saving of FM operator sequence (thanks [@elohimf], [@jpcima], [@OPNA2608]) - [#131] - Fix incorrect bit set for FM 3ch expanded mode (thanks [@nukeykt], [@jpcima]) [#122]: https://github.com/rerrahkr/BambooTracker/issues/122 [#123]: https://github.com/rerrahkr/BambooTracker/pull/123 [#125]: https://github.com/rerrahkr/BambooTracker/pull/125 [#124]: https://github.com/rerrahkr/BambooTracker/pull/124 [#96]: https://github.com/rerrahkr/BambooTracker/issues/96 [#126]: https://github.com/rerrahkr/BambooTracker/issues/126 [#127]: https://github.com/rerrahkr/BambooTracker/issues/127 [#128]: https://github.com/rerrahkr/BambooTracker/issues/128 [#129]: https://github.com/rerrahkr/BambooTracker/issues/129 [#130]: https://github.com/rerrahkr/BambooTracker/issues/130 [#133]: https://github.com/rerrahkr/BambooTracker/pull/133 [#135]: https://github.com/rerrahkr/BambooTracker/issues/135 [#131]: https://github.com/rerrahkr/BambooTracker/issues/131 [#134]: https://github.com/rerrahkr/BambooTracker/issues/134 [@N-SPC700]: https://github.com/N-SPC700 [Dippy]: https://www.youtube.com/channel/UCw2xCNQhuwpnfnf1-wfRefQ [@elohimf]: https://github.com/elohimf [@nukeykt]: https://github.com/nukeykt ## v0.2.0 (2019-04-30) ### Added - [#76], [#107] - MIDI keyboard support ([#32]; thanks [@galap-1], [@jpcima], [@OPNA2608]) - Add edit/jam button in tool bar - Add 1 sample module (thanks [@SuperJetSpade]) - Add 2nd pattern highlight - [#79] - Add BPM indicator in status bar (thanks [@papiezak]) - Add key repeat settings for pattern editor - [#62], [#84] - Add FM3ch expanded mode (thanks [@jimbo1qaz], [@OPNA2608]) - [#91] - Add module and instrument specification archive (thanks [@jpcima]) - [#88] - Add FM detune display mode selection (thanks [@bryc] and others) - [#89] - Add custom note entry layout configuration ([#70]; thanks [@OPNA2608], [@jpcima]) - [#3] - Add tone/hard frequency ratio in SSG envelope edit and tone/square-mask frequency ratio in SSG waveform edit (thanks [@marysiamzawka]) - [#94], [#106] - Add Nuked OPN-Mod emulator ([#93]; thanks [@jpcima], [@papiezak]) - [#103], [#105], [#110] - Add oscilloscope ([#79]; thanks [@jpcima], [@papiezak]) - [#99], [#111] - Add VGM and S98 chip target selection ([#98]; thanks [@jpcima], [@pedipanol]) - Add default file name to save/export dialogs - [#108] - Add macOS test to Travis CI (thanks [@OPNA2608], [@jpcima]) - Add resolution selection to S98 export settings - [#112] - Add effect list dialog (thanks [@attilaM68K]) - [#79] - Add plain keyboard shortcut list dialog (thanks [@papiezak]) ### Changed - [#82] - Change default main window size to 900x700 (thanks [@KamuiKazuma]) - Change to keep current instrument when loading module - Move tick frequency settings from module settings groupbox to module properties dialog - [#119] - Change FM instrument editor scrollable (thanks [@Delta-Psi]) - Improve groove editor - Change some layouts of UI - [#121] - Update translation: fr, ja (thanks [@jpcima], [@trebmuh]) ### Fixed - [#80] - Fix a typo in BambooTracker.fr.1 (thanks [@trebmuh]) - [#81] - Fix crash when toggling FM1 on/off using mouse (thanks [@papiezak]) - Fix operator mask in restarting - [#3] - Fix SSG square mask entry (thanks [@papiezak]) - [#85] - Fix Out-of-Path build for Qt version workaround ([#83]; thanks [@OPNA2608], [@jimbo1qaz]) - [#86] - Fix crash when changing pattern size during playback (thanks [@papiezak]) - Fix spinboxes to emit value change event when finishing text editing - [#88] - Fix incorrect DT importing from TFI/VGI (thanks [@bryc] and others) - [#87], [#95] - Fix FM envelope set association when renaming and type combobox overlapping (thanks [@OPNA2608]) - [#92] - Fix instrument declaration check on arpeggio effect (thanks [@attilaM68K]) - [#90] - Fix module and instrument specification documents (thanks [@jpcima]) - Fix bug increasing tone by octave when changing from Tri w to Saw in SSG waveform sequence - [#97] - Fix .y12 instrument loader (thanks [@jpcima]) - [#101] - Fix to update the current order position when deleting a order (thanks [@jpcima], [@OPNA2608]) - Fix SCCI to be launched when changing configuration - [#100], [#104] - Accelerate vgm/s98 export to skip sample generation (thanks [@jpcima]) - [#106] - Fix 12-bit wrapping behavior in MAME YM2608 ADPCM-A (thanks [@jpcima]) - [#109] - Fix icon to desplay all windows (thanks [@jpcima]) - [#114] - Fix incorrect event interpolation ([#113]; thanks [@jpcima]) - [#115] - Fix jam manager to replace key on data with new one when the same data is exist (thanks [@papiezak]) - Fix tick timings more accurate in using SCCI - [#116] - Fix position the cursor after setting line edit contents (thanks [@jpcima]) - [#117] - Resolve warning 'catching polymorphic type by value' (thanks [@jpcima]) - Add file save check before opening a module from recent files list - Fix incorrect wait counts in S98 exportation - Fix wait time precision of VGM and S98 - Fix channel state retrieve to stop instrument sequences when there is no note at the first step ### Removed - Remove module and instrument saving to past version - [#3] - Remove square-mask frequency selection by note+pitch - Delete module properties dialog open button in module settings groupbox [#80]: https://github.com/rerrahkr/BambooTracker/pull/80 [#81]: https://github.com/rerrahkr/BambooTracker/issues/81 [#76]: https://github.com/rerrahkr/BambooTracker/pull/76 [#32]: https://github.com/rerrahkr/BambooTracker/issues/32 [#79]: https://github.com/rerrahkr/BambooTracker/issues/79 [#62]: https://github.com/rerrahkr/BambooTracker/issues/62 [#82]: https://github.com/rerrahkr/BambooTracker/issues/82 [#3]: https://github.com/rerrahkr/BambooTracker/issues/3 [#84]: https://github.com/rerrahkr/BambooTracker/issues/84 [#85]: https://github.com/rerrahkr/BambooTracker/pull/85 [#83]: https://github.com/rerrahkr/BambooTracker/issues/83 [#86]: https://github.com/rerrahkr/BambooTracker/issues/86 [#88]: https://github.com/rerrahkr/BambooTracker/issues/88 [#87]: https://github.com/rerrahkr/BambooTracker/issues/87 [#91]: https://github.com/rerrahkr/BambooTracker/issues/91 [#92]: https://github.com/rerrahkr/BambooTracker/issues/92 [#89]: https://github.com/rerrahkr/BambooTracker/pull/89 [#95]: https://github.com/rerrahkr/BambooTracker/pull/95 [#90]: https://github.com/rerrahkr/BambooTracker/pull/90 [#94]: https://github.com/rerrahkr/BambooTracker/pull/94 [#93]: https://github.com/rerrahkr/BambooTracker/issues/93 [#97]: https://github.com/rerrahkr/BambooTracker/pull/97 [#101]: https://github.com/rerrahkr/BambooTracker/issues/101 [#100]: https://github.com/rerrahkr/BambooTracker/issues/100 [#103]: https://github.com/rerrahkr/BambooTracker/pull/103 [#104]: https://github.com/rerrahkr/BambooTracker/pull/104 [#105]: https://github.com/rerrahkr/BambooTracker/pull/105 [#99]: https://github.com/rerrahkr/BambooTracker/pull/99 [#98]: https://github.com/rerrahkr/BambooTracker/issues/98 [#106]: https://github.com/rerrahkr/BambooTracker/pull/106 [#107]: https://github.com/rerrahkr/BambooTracker/pull/107 [#108]: https://github.com/rerrahkr/BambooTracker/pull/108 [#109]: https://github.com/rerrahkr/BambooTracker/pull/109 [#111]: https://github.com/rerrahkr/BambooTracker/pull/111 [#110]: https://github.com/rerrahkr/BambooTracker/pull/110 [#114]: https://github.com/rerrahkr/BambooTracker/pull/114 [#113]: https://github.com/rerrahkr/BambooTracker/issues/113 [#115]: https://github.com/rerrahkr/BambooTracker/issues/115 [#116]: https://github.com/rerrahkr/BambooTracker/pull/116 [#117]: https://github.com/rerrahkr/BambooTracker/pull/117 [#119]: https://github.com/rerrahkr/BambooTracker/issues/119 [#112]: https://github.com/rerrahkr/BambooTracker/issues/112 [#121]: https://github.com/rerrahkr/BambooTracker/pull/121 [@SuperJetSpade]: https://twitter.com/SuperJetSpade [@KamuiKazuma]: https://github.com/KamuiKazuma [@bryc]: https://github.com/bryc [@attilaM68K]: https://github.com/attilaM68K [@Delta-Psi]: https://github.com/Delta-Psi ## v0.1.6 (2019-03-16) ### Added - [#63] - L10n: French, Japanese (thanks [@jpcima], [@trebmuh]) - Add file history - [#65] - Add repository to Appveyor and Travis CI (thanks [@papiezak], [@OPNA2608], [@jpcima]) - [#70] - Add keyboard layout selection for note entry (thanks [@OPNA2608]) - Add key shortcuts for instrument list - [#53] - Add 2 demo modules (thanks [@ehaupt], [@maakmusic]) ### Fixed - [#69] - Fix corruption in jamming (thanks [@maakmusic], [@ImATrackMan]) - [#61] - Fix translation building for Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. BambooTracker-0.3.5/README.md000066400000000000000000000500021362177441300155160ustar00rootroot00000000000000# ![icon](./img/icon.png) BambooTracker [![GitHub release](https://img.shields.io/badge/release-v0.3.5-brightgreen.svg)](https://github.com/rerrahkr/BambooTracker/releases) ![Platform: windows | macos | linux](https://img.shields.io/badge/platform-windows%20|%20macos%20|%20linux-lightgrey.svg) [![Travis CI Build Status](https://travis-ci.org/rerrahkr/BambooTracker.svg?branch=master)](https://travis-ci.org/rerrahkr/BambooTracker) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/jgg75iyduc1ij7ew?svg=true)](https://ci.appveyor.com/project/rerrahkr/bambootracker) [![LICENSE](https://img.shields.io/github/license/rerrahkr/BambooTracker.svg)](./LICENSE) ![Example](./img/overview.gif) BambooTracker is a music tracker for the Yamaha YM2608 (OPNA) sound chip which was used in NEC PC-8801/9801 series computers. [日本語](./README_ja.md) ## Downloads **On Windows**: - - *Development builds*: get "artifacts" from [Appveyor](https://ci.appveyor.com/project/rerrahkr/bambootracker) **On macOS**: - **On Linux**: - See below chapters "Build on Linux" - "Install package on Debian or Ubuntu". ## Wiki - [BambooTracker Wiki (GitHub Wiki)](https://github.com/rerrahkr/BambooTracker/wiki) ## Glossary The files created by this tracker are called "modules". One such module contains songs (song data), instruments (tone data) and settings common to each song. In a song, the channel of each sound source is assigned to a track, and the track holds multiple patterns (performance patterns). The patterns are played by registering them in the order they appear the song. (from beginning to end) A pattern describes a structure in which steps are arranged in chronological order. Key On / off and most effects are described in step units. A tick is the minimum performance unit, one step = n ticks. The effects (such as vibrato) which change with the count unit are based on ticks. ## Interface Overview ### Instrument List In the Instrument List, you control the instruments (tone data) used in the module. All songs in the module share the instruments registered here. Up to 128 instruments can be registered (`$00` - `$7F`). ### Instrument Editor Double-clicking an instrument opens the Instrument Editor and allows you to edit said instrument. Instruments can share their settings (properties) with other instruments. For instruments that share properties, a list of all users (instruments) of each property are displayed. Some instrument editors can set performance sequences. One column corresponds to one tick in the sequence editor. It also corresponds to specifying the sequence loop / release point. Left click to create point or increase count / type change, right click to delete point or decrease count. You can move the position by dragging the edge of the point. For the release type, you can select from the following three types only for envelope setting of SSG. - Fixed: Run at specified volume from release point at Key Off - Absolute: Run from the first point after the release point to reach the volume at Key Off - Relative: Execute from the release point with the volume set as the maximum volume at Key Off #### FM Editor In the FM Editor, you can configure the 1. Envelope 2. LFO 3. Operator Sequence 4. Arpeggio 5. Pitch of an FM instrument. It enables to paste plain text of envelope like MML. You can add and fix text formats in configuration. There are 8 default formats: FMP, FMP7, MMLDRV, MUCOM88, MXDRV, NRTDRV(`VOICE_MODE=0`), PMD, VOPM. Note the tracker reads digits from text in the order of appearance. Please remove needless digits contained in tone name or comments upon using default format. #### SSG Editor In the SSG Editor, you can configure the 1. Waveform 2. Tone/Noise 3. Envelope 4. Arpeggio 5. Pitch of an SSG instrument. The software envelope will be invalid while using waveforms other than rectangular waves. ### Order List In the Order List, we will register the pattern numbers in the order in which they will be played. The rows correspond to the order, and the columns correspond to tracks. The maximum length of the list is 256 (`$FF`). ### Pattern Editor In the pattern Editor, events such as Key On are registered in chronological order. One line represents one step. The columns of each track, from left to right, are: 1. Notes 2. Instrument numbers 3. Volume 4. Two characters before effect 1 5. Two characters after effect 1 6. Two characters before effect 2 7. Two characters after effect 2 8. Two characters before effect 3 9. Two characters after effect 3 10. Two characters before effect 4 11. Two characters after effect 4 Effects 2-3 can be displayed and hidden by pressing the + and - buttons on the header. Up to 256 patterns (`$00` - `$FF`) can be created for each track. ### Settings Field In the Settings Field you can adjust the module's metadata and performance settings of the song. #### Tempo Specify the tempo of the song. The tempo here is different from bpm, it is simply an indicator of speed. It can also be specified with the `0Fxx` effect. #### Speed Set the number of ticks for rough estimate in 1 step. Tempo may cause this value to change at run time. It can also be specified with the `0Fxx` effect. #### Pattern Size Sets the number of steps in the default pattern. The minimum value is 1, the maximum value is 256. #### Groove Specify the groove number set in the Groove Editor. When using groove, all other performance speed settings are fixed. It can also be specified with `0Oxx` effect. ## Key commands Please replace some keys with the following in macOS: - Ctrl → command - Alt → option - BackSpace → delete - Delete → fn+delete ### General | Key | Command | | ------ | ------------------------------ | | Ctrl+N | Create new module | | Ctrl+O | Open module | | Ctrl+S | Save module | | Ctrl+P | Open module property dialog | | Ctrl+I | Open current instrument editor | | Return | Play/stop song | | Space | Toggle jam/edit mode | | F1 | Show effect list dialog | | F2 | Focus on pattern editor | | F3 | Focus on order list | | F4 | Focus on instrument list | | F5 | Play from start | | F6 | Play pattern | | F7 | Play from current position | | F8 | Stop song | | F12 | Kill sound | ### Instrument list | Key | Command | | ------ | ------------------------------ | | Insert | Add instrument | | Delete | Remove instrument | | Ctrl+I | Open current instrument editor | ### Order list | Key | Command | | -------- | ------------------- | | Ctrl+C | Copy | | Ctrl+V | Paste | | Ctrl+A | Select track/all | | Ctrl+D | Duplicate order | | Alt+D | Clone order | | Home | Jump to first order | | End | Jump to last order | | PageUp | Jump to upper oder | | PageDown | Jump to lower oder | | Insert | Insert order below | | Delete | Delete order | | Escape | Deselect | ### Pattern editor | Key | Command | | ----------- | ---------------------------------- | | Ctrl+C | Copy | | Ctrl+X | Cut | | Ctrl+V | Paste | | Ctrl+M | Paste and mix | | Ctrl+A | Select track/all | | Ctrl+G | Interpolate | | Ctrl+R | Reverse | | Ctrl+F1 | Decrease note | | Ctrl+F2 | Increase note | | Ctrl+F3 | Decrease octave | | Ctrl+F4 | Increase octave | | Alt+F9 | Toggle track | | Alt+F10 | Solo track | | Alt+S | Replace instrument | | Alt+L | Expand effect column | | Alt+K | Shrink effect column | | Tab | Jump to right track | | BackTab | Jump to left track | | Home | Jump to first step | | End | Jump to last step | | PageUp | Jump to upper step | | PageDown | Jump to lower step | | Ctrl+Up | Jump to upper 1st highlighted step | | Ctrl+Down | Jump to lower 1st highlighted step | | Insert | Insert step | | BackSpace | Delete the step above | | Delete | Delete commands | | Escape | Deselection | | - | Key off | | \* (numpad) | Increase octave/echo buffer number | | / (numpad) | Decrease octave/echo buffer number | | ^ | Echo buffer access | There are two rows of a piano keyboard: ``` Current octave+1 2 3 5 6 7 9 Q W ER T Y UI O Current octave S D G H J L Z X CV B N M, . ``` The keyboard layout can be changed in the configuration. ## Volume range | | Minimum | Maximum | | ----- | ------- | ------- | | FM | 7F | 00 | | SSG | 00 | 0F | | Drums | 00 | 1F | It is able to reverse the order of FM volume (Configuration -> General -> Reverse FM volume order). ## Effect list | Effect | FM | SSG | Drums | | ------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------- | | 00xy | Arpeggio (x[0-F]: 2nd note, y[0-F]: 3rd note) | Arpeggio | - | | 01xx | Portamento up (xx[00-FF]: depth) | Portamento up | - | | 02xx | Portamento down (xx[00-FF]: depth) | Portamento down | - | | 03xx | Tone portamento (xx[00-FF]: depth) | Tone portamento | - | | 04xy | Vibrato (x[0-F]: period, y[0-F]: depth) | Vibrato | - | | 07xy | Tremolo (x[0-F]: period, y[0-F]: depth) | Tremolo | - | | 08xx | Pan (xx: 00=No sound, 01=Right, 02=Left, 03=Center) | - | Pan | | 0A0x | Volume slide down (x[0-F]: depth) | Volume slide down | - | | 0Ax0 | Volume slide up (x[0-F]: depth) | Volume slide up | - | | 0Bxx | Position jump (xx: order) | Position jump | Position jump | | 0Cxx | Song end (xx: any number) | Song end | Song end | | 0Dxx | Pattern break (xx: start step on the next order) | Pattern break | Pattern break | | 0Fxx | Speed/tempo change (xx[00-1F]: speed, [20-FF]: tempo) | Speed/tempo change | Speed/tempo change | | 0Gxx | Note delay (xx[00-FF]: count) | Note delay | Note delay | | 0Hxy | - | Auto envelope (x[0-F]: shift amount (x-8), y[0-F]: shape) | - | | 0Ixx | - | Hardware envelope period 1 (xx[00-FF]: high byte) | - | | 0Jxx | - | Hardware envelope period 2 (xx[00-FF]: low byte) | - | | 0Oxx | Groove (xx[00-FF]: number) | Groove | Groove | | 0Pxx | Detune (xx[00-FF]: pitch (xx-80)) | Detune | - | | 0Qxy | Note slide up (x[0-F]: count, y[0-F]: seminote) | Note slide up | - | | 0Rxy | Note slide down (x[0-F]: count, y[0-F]: seminote) | Note slide down | - | | 0Sxx | Note cut (xx[01-FF]: count) | Note cut | Note cut | | 0Txy | Transpose delay (x[1-7]: upwards count, [9-F]: downwards count (x-8), y[0-F]: seminote) | Transpose delay | - | | 0Vxx | - | Tone/Noise mix (xx: 00=No sound, 01=Tone, 02=Noise, 03=Tone&Noise) | Master volume (xx[00-3F]: volume) | | 0Wxx | - | Noise pitch (xx[00-1F]: pitch) | - | | 0Xxx | Register address bank 0 (xx[00-B6]: address) | Register address bank 0 | Registe address bank 0 | | 0Yxx | Register address bank 1 (xx[00-B6]: address) | Register address bank 1 | Registe address bank 1 | | 0Zxx | Regisetr value set (xx[00-FF]: value) | Register value set | Register value set | | Axyy | AR control (x[1-4]: operator, y[00-1F]: attack rate) | - | - | | B0xx | Brightness (xx[01-FF]: relative value (xx-80)) | - | - | | Dxyy | DR control (x[1-4]: operator, y[00-1F]: decay rate) | - | - | | FBxx | FB control (xx[00-07]: feedback value) | - | - | | Mxyy | Volume delay (x[1-F]: count, yy[00-FF]: volume) | Volume delay | Volume delay | | MLxy | ML control (x[1-4]: operator, y[0-F]: multiple) | - | - | | RRxy | RR control (x[1-4]: operator, y[0-F]: release rate) | - | - | | Txyy | TL control (x[1-4]: operator, yy[00-7F]: total level) | - | - | ## File I/O ### Module The tracker enables to open and save to .btm (BambooTracker module file). ### Instrument The tracker can load instrument from the following files. - .bti (BambooTracker instrument file) - .dmp (DefleMask preset file) - .tfi (TFM Music Maker instrument file) - .vgi (VGM Music Maker instrument file) - .opni (WOPN instrument file) - .y12 (Gens KMod dump file) - .ins (MVSTracker instrument file) It also supports loading plain text of FM envelope. A instrument saves as .bti file. ### Bank The tracker can load bank from the following files. - .btb (BambooTracker bank file) - .wopn (WOPN bank file) A bank saves as .btb file. ### Export The tracker can export a song to the following files. - .wav (WAVE file) - .vgm (VGM file) - .s98 (S98 file) ## Language BambooTracker supports following languages: - English (default) - French - Japanese ## Build on Linux On Ubuntu 18.04: ### Dependencies > make > Qt5 (including qmake) > Qt5 Multimedia > Qt5 Multimedia plugins > libasound2 ```bash sudo apt-get install \ build-essential \ qt5-default qtmultimedia5-dev libqt5multimedia5-plugins \ libasound2-dev ``` You also need to install `libpulse-dev` if you use PulseAudio, or `libjack-dev` if you use Jack. ### Compilation ```bash cd BambooTracker qmake make ``` If you use PulseAudio or JACK, add the option to `qmake`: - PulseAudio: `qmake DEFINES+=__LINUX_PULSE__` - JACK: `qmake DEFINES+=__UNIX_JACK__` ## Install package or build on FreeBSD ### Build To build the BambooTracker via FreeBSD ports ```bash cd /usr/ports/audio/bambootracker make install clean ``` ### Package To install the package ```bash pkg install bambootracker ``` ## Install package on Debian or Ubuntu `apt-get install bambootracker` ## Changelog *See [CHANGELOG.md](./CHANGELOG.md).* ## License This program and its source code are licensed under the GNU General License Version 2. *See [LICENSE](./LICENSE) and [list.md](./licenses/list.md) of libraries.* ## Credits I would like to thank the following people for making it: - Qt team for Qt framework - MAME team, and Valley Bell for the codes of YM2608 chip emuration - Mark James for the tool bar icons - VGMRips team for the VGM file format documentation - Some tracker creators, especially HertzDevil of 0CC-FamiTracker, Leonardo Demartino (delek) of Deflemask Tracker and Lasse Öörni (Cadaver) of GoatTracker for UI and routines of the tracker - Decidetto for the application icon - Vitaly Novichkov (Wohlstand) for WOPN instrument format files - maak, SuperJet Spade and Dippy for sample modules - papiezak for sample instruments - Ru^3 for S98 file format documentation - がし3 (gasshi) for SCCI libraries - honet for C86CTL libraries - Gary P. Scavone and others for RtAudio and RtMidi libraries - Thanks Alexey Khokholov (Nuke.YKT) and Jean Pierre Cimalando for Nuked OPN-Mod emulation - Jean Pierre Cimalando and Olivier Humbert for French translation - And everyone who helps this project! BambooTracker-0.3.5/README_ja.md000066400000000000000000000707421362177441300162050ustar00rootroot00000000000000# ![icon](./img/icon.png) BambooTracker [![GitHub release](https://img.shields.io/badge/release-v0.3.5-brightgreen.svg)](https://github.com/rerrahkr/BambooTracker/releases) ![Platform: windows | macos | linux](https://img.shields.io/badge/platform-windows%20|%20macos%20|%20linux-lightgrey.svg) [![Travis CI Build Status](https://travis-ci.org/rerrahkr/BambooTracker.svg?branch=master)](https://travis-ci.org/rerrahkr/BambooTracker) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/jgg75iyduc1ij7ew?svg=true)](https://ci.appveyor.com/project/rerrahkr/bambootracker) [![LICENSE](https://img.shields.io/github/license/rerrahkr/BambooTracker.svg)](./LICENSE) ![Example](./img/overview.gif) このアプリケーションはNEC PC-8801/9801シリーズに搭載されていたFM音源YM2608(OPNA)向けのトラッカーです。 [English](./README.md) ## ダウンロード **Windows**: - - *開発版*: [Appveyor](https://ci.appveyor.com/project/rerrahkr/bambootracker)から"Artifacts"をダウンロード **macOS**: - **Linux**: - "Linuxでのビルド方法" - "DebianまたはUbuntuでのパッケージのインストール"の章を参照してください。 ## Wiki 日本語版Wikiは[こちら](https://github.com/rerrahkr/BambooTracker/wiki/%E3%83%9B%E3%83%BC%E3%83%A0)です。 ## 用語解説 トラッカーで作成される1データ(ファイル)をモジュールと呼びます。モジュールは複数のソング(曲データ)とインストゥルメント(音色)、各ソング共通の設定を持ちます。 ソング中では各音源のチャンネルがトラックに割り当てられ、トラックはパターン(演奏パターン)を複数保持します。 パターンはソングのオーダーに登録することで演奏されます。オーダーはリストになっており、先頭から順に演奏されていきます。 パターンはステップが時系列順に並ぶ構造をしています。ノートオン/オフやほとんどのエフェクトはステップ単位で記述されます。 ティックは最小の演奏単位で、1ステップ=nティックといった関係になります。カウント単位で変化する効果(ビブラートなど)はティックを基準にしています。 ## インターフェース概要 ### インストゥルメントリスト インストゥルメントリストではモジュールで使用されるインストゥルメント(音色データ)を管理します。モジュール内の全てのソングはここに登録されているインストゥルメントを共有します。インストゥルメントは最大で128個登録できます($00-7F)。 ### インストゥルメントエディタ インストゥルメントをダブルクリックするとインストゥルメントエディタが開き、そのインストゥルメントを編集することができます。 インストゥルメントはその設定(プロパティ)を他のインストゥルメントと共有することができます。プロパティを共有しているインストゥルメントは各プロパティ内のUsersに番号が表示されます。 インストゥルメントエディタには演奏シーケンスを設定できるものがあります。シーケンスエディタは1列が1ティックに対応しています。 またシーケンスのループ/リリースポイントの指定にも対応しています。左クリックでポイント作成またはカウント増加/タイプ変更、右クリックでポイント削除またはカウント減少します。ポイントのエッジをドラッグすることで位置を移動できます。 リリースのタイプはSSGのエンベロープ設定のみ以下の3種類から選択できます。 - Fixed: キーオフ時にリリースポイントから指定通りの音量で実行 - Absolute: キーオフ時の音量に到達するリリースポイント後の最初の地点から実行 - Relative: キーオフ時に音量を最大音量としてリリースポイントから実行 #### FMエディタ FMエディタでは、 1. エンベロープ 2. LFO 3. オペレーターシーケンス 4. アルペジオ 5. ピッチ のプロパティを設定できます。 エンベロープはMMLなどのテキストデータを貼り付けして読み込むことができます。テキストフォーマットは設定で追加・変更を行うことができます。デフォルトではFMP、FMP7、MMLDRV、MUCOM88、MXDRV、NRTDRV(`VOICE_MODE=0`)、PMD、VOPMが登録されています。 この機能ではテキスト中の数字を出現順に読み込んでいきます。デフォルトのフォーマットを使用する際には予め音色名、コメントに含まれる数字を削除する必要があります。 #### SSGエディタ SSGエディタでは、 1. 波形 2. トーン/ノイズ 3. エンベロープ 4. アルペジオ 5. ピッチ のプロパティを設定できます。 矩形波以外の波形を使用している間はソフトウェアエンベロープは無効になります。 ### オーダーリスト オーダーリストではパターン番号を演奏する順番に登録していきます。行がオーダー、列がトラックに対応しています。 リストの最大長は256($FF)です。 ### パターンエディタ パターンエディタではノートオンなどのイベントを時系列順に登録していきます。行はステップを表します。列はトラック毎に左から 1. 音符 2. インストゥルメント番号 3. 音量 4. エフェクト1の前2文字 5. エフェクト1の後2文字 6. エフェクト2の前2文字 7. エフェクト2の後2文字 8. エフェクト3の前2文字 9. エフェクト3の後2文字 10. エフェクト4の前2文字 11. エフェクト4の後2文字 を表しています。エフェクト2-3はヘッダーの+, -ボタンで表示/非表示を選択できます。 パターンはトラックごとに最大で256個($00-FF)まで作成できます。 ### 設定フィールド 設定フィールドでは、モジュールのメタデータやソングの演奏についての設定が行えます。 #### Tempo ソングのテンポを指定します。なお、ここでのテンポはbpmとは異なり、単に早さの指標です。 0Fxxエフェクトで指定することもできます。 #### Speed 1ステップにおける目安のティック数を設定します。Tempoによってこの値は実行時に変化することがあります。 0Fxxエフェクトで指定することもできます。 #### Pattern size デフォルトのパターン中のステップの個数を設定します。最小値は1、最大値は256です。 #### Groove グルーヴエディタで設定したグルーヴ番号を指定します。グルーヴを利用する場合、他の演奏速度の設定はすべて固定されます。 0Oxxエフェクトで指定することもできます。 ## ショートカット macOSでは一部のキーを以下のものに置き換えてください。 - Ctrl → command - Alt → option - BackSpace → delete - Delete → fn+delete ### 全般 | キー | コマンド | | ------ | ------------------------------------------ | | Ctrl+N | モジュール新規作成 | | Ctrl+O | モジュールを開く | | Ctrl+S | モジュール保存 | | Ctrl+P | モジュール設定 | | Ctrl+I | 選択中のインストゥルメントのエディタを開く | | Return | 再生/停止 | | Space | Jam/Editモード切替 | | F1 | エフェクトリストダイアログを開く | | F2 | パターンエディタへ移動 | | F3 | オーダーリストへ移動 | | F4 | インストゥルメントリストへ移動 | | F5 | Songの最初から再生 | | F6 | パターンを再生 | | F7 | カーソル位置から再生 | | F8 | 停止 | | F12 | 音声リセット | ### インストゥルメントリスト | キー | コマンド | | ------ | ------------------------------------------ | | Insert | インストゥルメント追加 | | Delete | インストゥルメント削除 | | Ctrl+I | 選択中のインストゥルメントのエディタを開く | ### オーダーリスト | キー | コマンド | | -------- | ------------------------------------------------------ | | Ctrl+C | 選択範囲コピー | | Ctrl+V | 貼り付け | | Ctrl+A | 1回目:現在のトラックの全オーダ選択, 2回目:オーダ全選択 | | Ctrl+D | オーダーをコピーし次のオーダーとして挿入 | | Alt+D | 選択したパターンをクローンしてその位置に置き換える | | Home | 最初のオーダーへジャンプ | | End | 最後のオーダーへジャンプ | | PageUp | 前へオーダージャンプ | | PageDown | 後ろへオーダージャンプ | | Insert | オーダー挿入 | | Delete | 現在のオーダーを削除し詰める | | Escape | 選択解除 | ### パターンエディタ | キー | コマンド | | ------------- | -------------------------------------------------------------------------- | | Ctrl+C | 選択範囲コピー | | Ctrl+X | 選択範囲カット | | Ctrl+V | 貼り付け | | Ctrl+M | Mix貼り付け | | Ctrl+A | 1回目:現在のパターン・トラックの全ステップ選択, 2回目:現在のパターン全選択 | | Ctrl+G | 選択範囲のコマンド補完 | | Ctrl+R | 選択範囲のコマンドを逆転 | | Ctrl+F1 | 選択範囲または現在のノート半音下げる | | Ctrl+F2 | 選択範囲または現在のノート半音上げる | | Ctrl+F3 | 選択範囲または現在のノートオクターブ下げる | | Ctrl+F4 | 選択範囲または現在のノートオクターブ下げる | | Alt+F9 | 現在のトラックをミュート | | Alt+F10 | 現在以外のトラックをミュート | | Alt+S | 選択範囲のインストゥルメントを現在の物に変更 | | Alt+L | 現在のトラックのエフェクト列の表示数を増やす | | Alt+K | 現在のトラックのエフェクト列の表示数を減らす | | Tab | 次のトラックへ移動 | | BackTab | 前のトラックへ移動 | | Home | パターンの最初のステップへジャンプ | | End | パターンの最後のステップへジャンプ | | PageUp | 前へステップジャンプ | | PageDown | 後ろへステップジャンプ | | Ctrl+Up | 前のハイライトされた1stステップへジャンプ | | Ctrl+Down | 後ろのハイライトされた1stステップへジャンプ | | Insert | ステップ挿入 | | BackSpace | 前のステップを削除し詰める | | Delete | 現在のステップのデータ削除 | | Escape | 選択解除 | | - | キーオフ | | \* (テンキー) | 入力オクターブアップ/エコーバッファ番号選択 | | / (テンキー) | 入力オクターブダウン/エコーバッファ番号選択 | | ^ | エコーバッファ | 音符はキーボードを鍵盤に見立てて入力します。 キーボードの配置は環境設定で変更可能です。 ``` 現在のオクターブ+1 2 3 5 6 7 9 Q W ER T Y UI O 現在のオクターブ S D G H J L Z X CV B N M, . ``` ## 音量値 | | 最大 | 最小 | | ---- | ---- | ---- | | FM | 7F | 00 | | SSG | 00 | 0F | | Drum | 00 | 1F | FMの順番は逆転することができます。 (Configuration -> General -> Reverse FM volume order). ## パターンエフェクト | エフェクト | FM | SSG | ドラム | | ---------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------ | | 00xy | アルペジオ (x[0-F]: 第2音, y[0-F]: 第3音) | アルペジオ | - | | 01xx | ポルタメント・アップ (xx[00-FF]: デプス) | ポルタメント・アップ | - | | 02xx | ポルタメント・ダウン (xx[00-FF]: デプス | ポルタメント・ダウン | - | | 03xx | トーン・ポルタメント (xx[00-FF]: デプス) | トーン・ポルタメント | - | | 04xy | ビブラート (x[0-F]: ピリオド, y[0-F]: デプス) | ビブラート | - | | 07xy | トレモロ (x[0-F]: ピリオド, y[0-F]: デプス) | トレモロ | - | | 08xx | パン (xx: 00=無音, 01=右, 02=左, 03=中央) | - | パン | | 0A0x | ボリューム・スライドダウン (x[0-F]: デプス) | ボリューム・スライドダウン | - | | 0Ax0 | ボリューム・スライドアップ (x[0-F]: デプス) | ボリューム・スライドアップ | - | | 0Bxx | ポジションジャンプ (xx: オーダ番号) | ポジションジャンプ | ポジションジャンプ | | 0Cxx | ソング・エンド (xx: 任意の値) | ソング・エンド | ソング・エンド | | 0Dxx | パターン・ブレーク (xx: 次のオーダの開始ステップ) | パターン・ブレーク | パターン・ブレーク | | 0Fxx | スピード/テンポチェンジ (xx[00-1F]: スピード, [20-FF]: テンポ) | スピード/テンポチェンジ | スピード/テンポチェンジ | | 0Gxx | ノート・ディレイ (xx[00-FF]: ディレイカウント) | ノート・ディレイ | ノート・ディレイ | | 0Hxy | - | オートエンベロープ (x[0-F]: シフト量 (x-8), y[0-F]: エンベロープ形状) | - | | 0Ixx | - | ハードウェアエンベロープ周期1 (xx[00-FF]: 上位バイト) | - | | 0Jxx | - | ハードウェアエンベロープ周期2 (xx[00-FF]: 下位バイト) | - | | 0Oxx | グルーブ (xx[00-FF]: グルーブ番号) | グルーブ | グルーブ | | 0Pxx | デチューン (xx[00-FF]: xx-80だけデチューン) | デチューン | - | | 0Qxy | ノート・スライドアップ (x[0-F]: カウント, y[0-F]: 半音数) | ノート・スライドアップ | - | | 0Rxy | ノート・スライドダウン (x[0-F]: カウント, y[0-F]: 半音数) | ノート・スライドダウン | - | | 0Sxx | ノート・カット (xx[01-FF]: ディレイカウント) | ノート・カット | ノート・カット | | 0Txy | トランスポーズ・ディレイ (x[1-7]: ディレイカウント(上向き変化), [9-F]: x-8分ディレイカウント(下向き変化), y[0-F]: 半音数) | トランスポーズ・ディレイ | - | | 0Vxx | - | トーン/ノイズ・ミックス (xx: 00=消音, 01=トーン, 02=ノイズ, 03=トーン&ノイズ) | マスターボリューム (xx[00-3F]: 音量) | | 0Wxx | - | ノイズ・ピッチ (xx[00-1F]: ピッチ) | - | | 0Xxx | レジスタ番地指定 バンク0 (xx[00-B6]: 番地) | レジスタ番地指定 バンク0 | レジスタ番地指定 バンク0 | | 0Yxx | レジスタ番地指定 バンク1 (xx[00-B6]: 番地) | レジスタ番地指定 バンク1 | レジスタ番地指定 バンク1 | | 0Zxx | レジスタ値指定 (xx[00-FF]: レジスタ値) | レジスタ値指定 | レジスタ値指定 | | Axyy | ARコントロール (x[1-4]: オペレーター, yy[00-1F]: AR値) | - | - | | B0xx | ブライトネス (xx[01-FF]: 相対値 (xx-80)) | | | | Dxyy | DRコントロール (x[1-4]: オペレーター, yy[00-1F]: DR値) | - | - | | FBxx | FBコントロール (xx[00-07]: フィードバック値) | - | - | | Mxyy | ボリューム・ディレイ (x[1-F]: ディレイカウント, yy[00-FF]: 音量) | ボリューム・ディレイ | ボリューム・ディレイ | | MLxy | MLコントロール (x[1-4]: オペレーター, y[0-F]: ML値) | - | - | | RRxy | RRコントロール (x[1-4]: オペレーター, y[0-F]: RR値) | - | - | | Txyy | TLコントロール (x[1-4]: オペレーター, yy[00-7F]: TL値) | - | - | ## ファイル入出力 ### モジュール .btm (BambooTracker module file)の読み込み・書き出しに対応しています。 ### インストゥルメント 以下のフォーマットの読み込み対応しています。 - .bti (BambooTracker instrument file) - .dmp (DefleMask preset file) - .tfi (TFM Music Maker instrument file) - .vgi (VGM Music Maker instrument file) - .opni (WOPN instrument file) - .y12 (Gens KMod dump file) - .ins (MVSTracker instrument file) また、FMエンベロープはプレーンテキストの読み込みにも対応しています。 書き出しは.btiのみ可能です。 ### バンク バンクは複数のインストゥルメントを1つのセットにまとめたものです。 以下のフォーマットの読み込みに対応しています。 - .btb (BambooTracker bank file) - .wopn (WOPN bank file) 書き出しは.btbのみ対応しています。 ### エクスポート 以下のファイルにエクスポートが可能です。 - .wav (WAVE file) - .vgm (VGM file) - .s98 (S98 file) ## 言語 BambooTrackerでは以下の言語に対応しています。 - 英語 (デフォルト) - フランス語 - 日本語 ## Linuxでのビルド方法 Ubuntu 18.04: ### 必要なパッケージ > make > Qt5 (qmake) > Qt5 Multimedia > Qt5 Multimedia plugins > libasound2 ```bash sudo apt-get install \ build-essential \ qt5-default qtmultimedia5-dev libqt5multimedia5-plugins \ libasound2-dev ``` PulseAudioを使用する場合は`libpulse-dev`を、JACKを使用する場合は`libjack-dev`もインストールして下さい。 ### コンパイル ```bash cd BambooTracker qmake make ``` PulseAudioやJACKを使用する場合は、`qmake`に以下のオプションを付けて下さい。 - PulseAudio: `qmake DEFINES+=__LINUX_PULSE__` - JACK: `qmake DEFINES+=__UNIX_JACK__` ## FreeBSDでのパッケージのインストールまたはビルド ### ビルド To build the BambooTracker via FreeBSD ports ```bash cd /usr/ports/audio/bambootracker make install clean ``` ### パッケージ To install the package ```bash pkg install bambootracker ``` ## DebianまたはUbuntuでのパッケージのインストール `apt-get install bambootracker` ## Changelog *[CHANGELOG.md](./CHANGELOG.md)を参照してください。* ## License このプログラム及びソースコードのライセンスははGNU General License Version 2です。 *[LICENSE](./LICENSE)とライブラリに関して[list.md](./licenses/list.md)も参照してください。* ## クレジット このアプリの作成において、以下の方々にお世話になっております。ありがとうございます。 - Qt - YM2608のエミュレーションコードに関してMAME開発チームさんとValley Bellさん - ツールバーに使用しているアイコンに関してMark Jamesさん - VGMファイルのフォーマットのドキュメントに関してVGMRipsチームの方々 - トラッカーのUIやルーチンに関して多くのトラッカー作成者様、特に0CC-FamiTrackerのHertzDevilさん、Deflemask TrackerのLeonardo Demartino (delek)さん、GoatTrackerのLasse Öörni (Cadaver)さん - トラッカーのアイコンに関してDecidettoさん - サンプル曲を提供してくださったmaakさん、SuperJet Spadeさん、Dippyさん - サンプルインストゥルメントを提供してくださったpapiezakさん - WOPNインストゥルメントのフォーマットファイルに関してVitaly Novichkov (Wohlstand)さん - S98ファイルのフォーマットのドキュメントに関してRu^3さん - SCCIライブラリに関してがし3さん - C86CTLライブラリに関してhonetさん - RtAudioとRtMidiライブラリに関してGary P. Scavoneさん他 - Nuked OPN-Modエミュレーションに関してAlexey Khokholov (Nuke.YKT)さん、Jean Pierre Cimalandoさん - フランス語翻訳に関してJean Pierre Cimalandoさん、Olivier Humbertさん - そしてこのプロジェクトに手助けしてくださるみなさん! BambooTracker-0.3.5/demos/000077500000000000000000000000001362177441300153515ustar00rootroot00000000000000BambooTracker-0.3.5/demos/demos.md000066400000000000000000000007121362177441300170020ustar00rootroot00000000000000## Sample modules - [maak] - breeze 2608, Rude Buster - Rerrah - Lotus - [Dippy] - Neo Megalopolis - [SuperJet Spade] - Underwater Ruins ## Sample instruments - [papiezak] - 2 banks (32 instruments) - Rerrah - 3 banks (54 instruments) Thanks all for samples! [Dippy]: https://www.youtube.com/channel/UCw2xCNQhuwpnfnf1-wfRefQ [maak]: https://twitter.com/maakmusic [papiezak]: https://github.com/papiezak [SuperJet Spade]: https://twitter.com/SuperJetSpade BambooTracker-0.3.5/demos/instruments/000077500000000000000000000000001362177441300177445ustar00rootroot00000000000000BambooTracker-0.3.5/demos/instruments/Rerrah/000077500000000000000000000000001362177441300211675ustar00rootroot00000000000000BambooTracker-0.3.5/demos/instruments/Rerrah/FM.btb000066400000000000000000000063041362177441300221650ustar00rootroot00000000000000BambooTrackerBnk INSTRMNT BBass 1BBass 2BBass 3H Synth bass 1H Synth bass 2APianoH Double pianoLElectric piano 1LElectric piano 2 LElectric piano 3 DClavinet I Synth piano 1 I Synth piano 2 I Synth piano 3 BLead 1BLead 2BLead 3BLead 4BLead 5DGuitar 1DGuitar 2AFlute?PadCMarimbaBBell 1BBell 2G Bass drum 1G Bass drum 2F Snare drumDOpen hatE Close hatBHi-HatINSTPROP ?&$?&-?*d&?*&? (!? ,?`? %?`7? G(?@?`Y6?/9`?d? w6?0?? ? uF??`f??D?`P?`H?`P?`HE?? ?@? 5?"; ?`"; C?*7 w? "?%n $??`?`=` E742`8?`(?`8 E?P? x?oX%? X F:(*5 c9?$h5W? `4 7?'W?Qh G???@7WG?`? `?? G? `? w?P&< GG?? (?`?h%? W ??`"275`"5@ ?o 5`@?@?@?h<2JD?cy(r?)(rG?9 x?X? xE<`4 v?@5 FD?`(?G?-?(????????G????G?`?g? ?G?`?? ?G?`? ? ?  ( L0.,*(0.,*/), }{y{} BambooTracker-0.3.5/demos/instruments/Rerrah/PMD-like Drums.btb000066400000000000000000000017751362177441300243070ustar00rootroot00000000000000BambooTrackerBnkINSTRMNT  Bass Drum Snare Drum 1 Snare Drum 2 Crash CymbalLow Tom Middle TomHigh TomRim Shot Hi-hat Close  Hi-hat Open  Ride CymbalINSTPROP1 !9:;<=>?@( H !"#$%&'()*+,-./0123456789:;<=>?@ @;@  @2   $  3  $'#  O( (#!(,)'%#"  I7 T4 BambooTracker-0.3.5/demos/instruments/Rerrah/SSG.btb000066400000000000000000000025251362177441300223200ustar00rootroot00000000000000BambooTrackerBnkEINSTRMNT  Bass Drum 1 Bass Drum 2 Snare Drum 1 Snare Drum 2 Snare Drum 3Hat Crash CymbalSSG 1SSG 2 SSG 3 SSG 4 INSTPROP&1   @ ('&%$#"!( H @?>=<;:9876543210/.-,+*)('&%$#"!2 2  z                  m 3   $&#  OBambooTracker-0.3.5/demos/instruments/papiezak/000077500000000000000000000000001362177441300215505ustar00rootroot00000000000000BambooTracker-0.3.5/demos/instruments/papiezak/FM.btb000066400000000000000000000052661362177441300225540ustar00rootroot00000000000000BambooTrackerBnk INSTRMNTDBass PadI Electric Bass E Slap Bass APiano H Octave Piano NDX7 Electric Piano F Synth KeysH Church OrganE Accordion E Harmonica CStrings [Analog Pad + Vibraphone CH3MODE MOverdriven Guitar CTrumpetABrassCBrass 2E SaxophoneDClarinetCMarimbaF VibraphoneBChimesLWind Chime (F#5)E Kick DrumASnareG Open Hi-hat I Closed Hi-hat!CTimpani"INSTPROP@?( ?( '?? d%2? d%? <G ?.f'&?.'?0d G<@h0#:*g&<@0!:*& G5b$(8 &5$#8 d& G<f!$:,f5<@ :+4D??(?`?`(v????G?.(?`.`(4`"4 /0 ..G8/8`/`F?*' ?*?L68a :8:8:` W???????G?``?`7?`?7W?h?h?h?hW?$5$?$5G??(?`?`(G?(?x?l(?ixg?Ȁ ?O? ? OG?5d ?07?5ld?0h7E230 3.V?`?`?`?`G??0< ? ?O | G?? O?`?n_!G????_"? g%>%? 0? (  $ BambooTracker-0.3.5/demos/instruments/papiezak/SSG.btb000066400000000000000000000014331362177441300226760ustar00rootroot00000000000000BambooTrackerBnk INSTRMNTtSSGKickSnare Open Hi-hat Closed Hi-hatINSTPROP01    + % #2P  V h  >  3,  "! BambooTracker-0.3.5/demos/modules/000077500000000000000000000000001362177441300170215ustar00rootroot00000000000000BambooTracker-0.3.5/demos/modules/Lotus.btm000066400000000000000000000120061362177441300206320ustar00rootroot00000000000000BambooTrackerModMODULE <LotusRerrah2019Cover of SID tune<INSTRMNT] SSG 1 Bass Drum Snare DrumHat8Bass9Piano> Pick Synth:Lead 1SSG 2INSTPROP6?0?? ? uF??`f??g??V?`7?'G?? (?`?h )F }{y{} 1   2      $  T  3  4F }{y{} GROOVE SONG zt?G''%%% #&#(0 6 8< F''**% #&#(0 6 8< > 08   +,/0?;   +,/0?> 08   +,/0 ?;   +,/0?>" 08  "" +,/0?;"  "" +,/0?> 08   "+,"/0"?;   "+,"/0"?\ F08KF MK NM"N 08#$M%(N08)*M+.N/0M1F08KF MK RM"R 08#$M%(R08)*M+.R/0M1W80Q"088 363 $80Q"(8,6084366:8<:L:8 3636 80Q"$8(6,80841<3L:8 683. .0Q!&3,6054665<3O10QB086; :03{7030T!6.04154665<163.$'(),*0(1)4,81<5F50QA5 1,15 50T!&5,30.6,<*F')03*03, ,0Q"(',.4,<.>103 H303. 50T!51 303(.03,{8030QB48<5 N65 313 .(/03*.,,0324"68%<) V(0T!") *03 )%03'+,$*,)48 03<" * {&030Q,0)034*8)<'t. 35  655650. 243685:<.>s. 35  :55:505 246685:<3>E p0P.  3 56556 "5$6. 8:3<>5t..  3 5:55: "5$65 8:6<>5s3 . 3 56556 "5$6. 8:3<>5]3 80Q" 8363"&80Q"*8.6286386<8>:L: 83636"80Q"&8*6.82861>3S80Q" 8363"&80Q"*8.6286386<8>:L: 8683.".0Q!(3.6256685>3S80Q" 8363"&80Q"*8.6286386<8>:N10QB6 ;:03{7030T! 6024356685>1 63.&'*).*2(3)6,:1>5 F50QA51,15"50T!(5.32.8,>* >')03*03,",0Q"*'..6,>. P103303 .50T!51"303*.03.{8030QB68>5 N6 5313".*/03,..,0346"8:%>)V(0T!" ) *03)%03'+,&*.)6: 03>"*{&030Q.2)036*:)>'xZ$$$ $$$$$$ $$$($,$0$4$8$<$ C% (08  '  fH08  $(,048< BambooTracker-0.3.5/demos/modules/Neo Megalopolis.btm000066400000000000000000000360551362177441300225130ustar00rootroot00000000000000BambooTrackerMod<MODULE a"Neo Megalopolis (Game: Hyper Zone)Jun Ishikawa (cover: dippy)1991<INSTRMNT I WGKickNew+TomCWGSnare reverse cymhatF WGPickBassF WGElecBassF Elec Piano@SoftBChords AFlute tone DWGSynth3 hat openINSTPROP ?l?`?`?`G?? ?? ?`#?`!?`? ` D$W?F??F? h ?B? ?  ? `? ? A? #? ? ?  ?F ?(?? p+)7" % @? ?? ? u? 3-(? ?`1M  2&   GROOVE SONG 55?a    $$$$$ $ $$$$$$$$$$ $"$$$&$($*$,$.$0$2$4$6$8$:$<$>$$$$$$ $ $$$$$$$$$$ $"$$$&$($*+0S,-0S./0S0 10S2!30S450S670S890S:;0S<=0S>?0S$$$$$ $ $$$$$$$$$$ $"$$$&$($*$,-0S./0S0#10S2#30S4 50S6 70S890S:;0S<=0S>%M0S0S0S0S 0S $ 0S ' 0S0S0S0S0S+0S0S0S+0S  !0S" #0S$,%0S& '0S( )0S* +0S,,-0S. /0S010S230S450S6+70S890S:;0S<+?M0S0S)0S0S 0S $ 0S ' 0S0S0S0S0S+0S0S+0S&0S0S !!0S"!#0S$!%0S&-'0S(!)0S*!+0S,-/0S010S230S450S6*70S890S:;0S<*?O0S0S)0S0S 0S $ 0S ' 0S0S0S0S0S+0S0S+0S&0S0S !!0S"!#0S$!%0S&-'0S(!)0S*!+0S,-/0S0%10S230S4%70S890S:%;0S< =0S>?0S%%,,1 1 %%,,11%%11 '"'$.&.(3*3,'.'0.2.43638':'<3>3$$++0 0 $$))00$$00 )")$0&0(5*5,).+0253703 560037203 9:03<7> $$++0 0 $$))00$$00 )")$0&0(5*5,).+0)2+034.0350S6003830390S:%?0S $.0-. $ "$+030503" $"0$$&$(.*$,$..0$2-4$6$8.:$<2=303 $$$$$ $ $$$$$$$$$$ $"$$$&$($*$,-0S./0S0#10S2#30S4 50S6 70S890S:;0S<=0S>% $0303 0 530.$"& $"$"&$('*),+..02046.8:+<$?H   :8 08040S80S80S: 0S: 0S : 0S : 0S:0S: 0S:0S:0S< 0S<0S<0S<0S<0S 9 !0S"9#0S$9%0S&: '0S(:)0S*:+0S,8 0041804:G08<>J08C: 080S:0S:0S< 0S< 0S < 0S < 0S<0SA 0SA0SA0S? 0S?0S?0S?0S?0S < !0S"<#0S$<%0S&A '0S(A)0S*A+0S,> 00414O040878cJ08;< ?0Sg3 080S3 0S3 0S5 0S5  0S 5  0S 5 0S5 0S8 0S8 0S8 0S: 0S: 0S: 0S: 0S: 0S 8 $04#-`0S.< 0402g5083c703 56c0037c203 9:c03<7> $g3 080S3 0S3 0S5 0S5  0S 5  0S 5 0S5 0S8 0S8 0S8 0S: 0S: 0S: 0S: 0S: 0S 8 $04#(0 10S2 30S4" 50S6$ 70S8' 90S:G <04'?x0S04 C: 080S:0S:0S< 0S< 0S < 0S < 0S<0SA 0SA0SA0S? 0S?0S?0S?0S?0S < !0S"<#0S$<%0S&A '0S(A)0S*A+0S,> 00414O040878cJ08; !0S">#0S$>%0S&? '0S(?)0S*?+0S,F 0041804:@08<>B08CA 080SA0SA0SC 0SC 0S C 0S C 0SC0SH 0SH0SH0SF 0SF0SF0SF0SF0S C !0S"C#0S$C%0S&H '0S(H)0S*H+0S,E 00414H040878cE08;?1C 08: ;,903<03?2C 08: 7/05<03?g? 080S? 0S? 0SA 0SA  0S A  0S A 0SA 0SB 0SB 0SB 0SA 0SA 0SA 0SA 0SA 0S B !0S"B #0S$B %0S&D '0S(D )0S*D +0S,D -0S.D /0S0F 10S2F 30S4F 50S6D 70S8D 90S:D ;0S<D =0S>D ?0Sg< 080S< 0S< 0S> 0S>  0S >  0S > 0S> 0S? 0S? 0S? 0SA 0SA 0SA 0SA 0SA 0S C $04#-`0S.C 040 $g< 080S< 0S< 0S> 0S>  0S >  0S > 0S> 0S? 0S? 0S? 0SA 0SA 0SA 0SA 0SA 0S C $04#(05 10S27 30S4: 50S6< 70S8? 90S:R <04'?x0S04 CA 080SA0SA0SC 0SC 0S C 0S C 0SC0SH 0SH0SH0SF 0SF0SF0SF0SF0S C !0S"C#0S$C%0S&H '0S(H)0S*H+0S,E 00414H040878cE08;C? 080S?0S?0SA 0SA 0S A 0S A 0SA0SF 0SF0SF0SD 0SD0SD0SD0SD0S A !0S"A#0S$A%0S&F '0S(F)0S*F+0S,C 004146O 04089:cJ08=>cF08/? 0408A ?/0<?)< 089 <<03?2< 089 </0<<03?g; 080S; 0S; 0S= 0S=  0S =  0S = 0S= 0S? 0S? 0S? 0S= 0S= 0S= 0S= 0S= 0S = !0S"= #0S$= %0S&A '0S(A )0S*A +0S,A -0S.A /0S0B 10S2B 30S4B 50S6A 70S8A 90S:A ;0S<A =0S>A ?0Sg7 080S7 0S7 0S: 0S:  0S :  0S : 0S: 0S8 0S8 0S8 0S? 0S? 0S? 0S? 0S? 0S < $04#-`0S.> 040 $g7 080S7 0S7 0S: 0S:  0S :  0S : 0S: 0S8 0S8 0S8 0S? 0S? 0S? 0S? 0S? 0S < $04#(0) 10S2+ 30S4. 50S60 70S83 90S:M <04'?x0S04  C? 080S?0S?0SA 0SA 0S A 0S A 0SA0SF 0SF0SF0SD 0SD0SD0SD0SD0S A !0S"A#0S$A%0S&F '0S(F)0S*F+0S,C 004146O 04089:cJ08=>cF08w    04"$C (04#1`0S2C 044 $$C (04#* 0<R >04'3   1002/000 0 000De702_002O702_702_002O702_ 702_(002O0702_4702_8002O>702_m702_002O702_702_702_002O 702_(002O,702_0702_2002O6702_8002O|702_002O702_702_702_002O 702_(002O,702_0702_2002O6702_8002O:002O>702_:002/<+}702_002O 702_702_702_702_002O 702_(002O,702_.702_2002O8702_:002O>702_702_002O 702_702_702_702_002O 702_(002O,702_.702_2002O4002O8002O:002O>702_ }702_002O 702_702_702_702_002O 702_(002O*702_.702_0002O4002O8002O:002O D702_702_702_702_ 702_(702_0702_8702_ |702_002O702_702_702_002O 702_(002O,702_0702_2002O6702_8002O:002O>702_ \702_702_702_702_ 702_(702_.002O0702_2002O8002O<002O d702_702_ 002O702_702_002O 702_(702_,002O0702_8702_<002Ot702_702_ 002O702_702_002O 702_(002O.702_2002O6702_8002O:002O<002O   < <<< < < < < < <<< < <<< < <"<$< &< (<*<,< .< 0<2<4< 6< 8<:<<< >< 7 04' 0S 5 04 0S3 0S5 04'0S3 040S5 0S 7 $04'+0S,< 04-0S.7 /0S05 404'50S6: 04:2 0G;0S<3 =0S>5 ?0Sx3 04' 0S 2 04 0S0 0S2 04'0S. 0404'0S 0 04(04'/ 004<+ ?0S~3 04' 0S 2 04 0S0 0S2 04'0S. 0404'0S 0 04(04',{< 03044x04'03?x0S04<<< < < < < < <<< < <<< < <"<$< &< (<*<,< .< 0<2<4< 6< 8<:<<< >7 <0 < < < < < < <<< < <<< < <"<$< &< (<*<,< .< 0<2<4< 6< 8<:<<< >< }000 0 0 0 000 000 0"0$0 (0*0,0 002040 80:0<0 <<< < < < < < <<< < <<< < <"<$< &< (<*<,< .< 0<2<4< 6< 8<9 :<<< >< ~000 0 0 0 000 000 0"0$0 (0*0,0 002040 80:0<7 ~000 0 0 0 000 000 0"0$0 (0*0,0 002040 80:0<7 <<< < < < < < <<< < <<< < <"<$< &< (<*<,< .< 0<2<4< 6< 8<:<<< >< ?0B4   0P7 04! 0S5 0S3 0S5 0S3 0S 5 !0S"7 -0S.< /0S07 10S25 604'70S8: <2 0G=0S>3 ?0S5 04!0S3 04' 0S2 040S0 0S2 04'0S. 0404'!0S"0 04*04'1 204>+ 04!|3 04!04' 0S2 040S0 0S2 04'0S. 0404'!0S"0 04*04'.{< 0304604'903>2 040P 7  :5 7  <2 <3 +   0P7 04!0S5 0S3 0S5 0S 3 !0S"5 #0S$7 /0S0< 10S27 30S45 804'90S:: >2 0G?0S3 04!0S5 0S3 04'0S2 040S0 0S2 04'0S. 04 04'#0S$0 04,04'3 404|3 04!04'0S2 040S0 0S2 04'0S. 04 04'#0S$0 04,04'0{< 0304804';03>0 040P 2  :1 2  <0 <. #    #    #   #   #  #  BambooTracker-0.3.5/demos/modules/Rude Buster.btm000066400000000000000000000303361362177441300216560ustar00rootroot00000000000000BambooTrackerMod0MODULE ?Deltarune - Rude BusterToby Fox<INSTRMNT9 @bassApianoE piano low@kickAsnareAclose@open@bell ssg piano Bmelody ?tom  Bguiter E guiter rr INSTPROP3 "? ? +? 8? G?`9 7??K7G?`9 7??K7V????G?? /? ?/G????G??hO??F;$`#?G?')?G?!27 ?`?a7 7??5da??  ? 2??  ? 2?? ( H ,'" /) s02(R  GROOVE SONG ** 0P*%  *  % +&+ $#%&'()*#+,-.2%34 5678%<@D*EF%GHIJ*KL%MNR+ST&UVWX+\`d&efj%lmnr%st#uv!wx yz{|}~&  +% $&&*,+0!46!:<%@FJLNPTV`&d!fjl!np%t x|%&0S  !  & (0S%#! #$&0S&)*!+,&-.0!34*0S6,9:(;<%=>!?@CD&0SFIJ!KL&MNPST(0SVYZ![\#]^%_`*cd&0Sfij!kl&mn!op%st)uv,yz){|%}~ }*%  *  % +&+ $&%&!'()*&+,-.2(34#5678(<@D*EF%GHIJ*KL%MNR+ST&UVWX+\`d&efj%lmnr%st#uv!wx yz{|}~&!  &  ! % )%  $*%&%'()**+,%-.2%34 5678(9:%;<!=>?@D#EFGHIJ#KLMNR)ST%UV WX)YZ%[\ ]^%_`f l!t4u6v4x-yz,{|*}~(&!  &  ! % )%  $*%&%'()**+,%-.2%34 5678(9:%;<!=>?@D#EFGHIJ#KLMNR)ST%UV WX)YZ%[\ ]^%_`dfhnvxz|!~ 0000 0000000 0$0&0(0,0.0204080<0>0@0D0F0H0L0N0R0T0X0\0^0`0d0f0h0l0n0r0t0x0|0~00; 0 00000; 0$; (0,0.00060:0<; @0D; H0L0N0P0V0Z0\; `0d; h0l0n0p0r0t0x0|0~00000 0000000 0$0&0(0,0.0204080:0;0<0>0@0D0F0H0L0N0R0T0X0\0^0`0d0h0l0n0r0t0x0|0~0  0P     $&'( ),-. 2456 78:<>@DFGHILMNRTUVWXZ\^`df hnxG 046@PTV`p   $*+.268<>@DJKNRVX\^`df hnvxyI  $. 2@ DNR`flg  $. 2@ DNR`df hnvxz|~ 0P08   $ &* ,248:< >DFJLRTXZ\^`bfhnvxz|~0P08   ! # % # # (  % & (* ,.! 0 24 6 >D F H J! L# N% PR# TV# XZ( \`* f, hj( ln* |B@ > =;=6 &B(@*>,=.;0=6D@FBH@J>L=N;P=V6\8`9f;hj>ln=xB@ > =;=6 &B(@*>,=.;0=6D@FBH@J>L=N;P=V6\8`9f8hj9ln8tx6|56|* ~, - 0P0202  * 02- / 0202 - 02/ 1 $4 &/ (1 03@)/ *- 03,./ 02- 464 8:2 <>1 A02B02 DH6 02JL1 P/ R1 03@S/ 03T- X/ \- ^/ _1 03d03ln* r* tv* x* z6 |* ~@4 1 - 8 4 1 / 4 ; 7 1 7 4 1 / 4 6 "1 $- &8 (4 *1 ,- .4 09 28 44 61 8/ :- <2 >4 @6 B* D1 F/ H* J1 L4 N6 P* R1 T/ V* X/ \4 `* bd* 0Sf6 h1 j* ln* t01v01w01x01y01 z01{0101&   - , - , 0S* ) , - (1 *,4 .06 18 0326 0341 036/ 8:- >@* B& 03D# 03F, H( 03J# 03L, N- 03P/ 03R, 03T) 03V4 X, Z2 \1 03^/ 03`1 f4 lg6 04Cx02|% 0204* - / 1 3 03 1 / 03/ 4  1 "- $* &1 (*4 ,./ /1 0/ <- >, @* B& D# F- H* J& L* N- P1 R/ T, V5 X1 Z, \1 ^5 `* bd* f6 hj* ln* z 0P08   $&*,248:<>DFJLRTXZ\^`bfhnvxz|~0P08     ! # % # #  ( "&% , .0 24! 6 8: < DJ L N P! R# T% VX# Z\# ^`( bf* l, np( rt*  B@>=;=6&,B.@0>2=4;6=<DFLBN@P>R=T;V=\6b8f9l;np>rt=~;9 7 6466&;(9*7,6.408<DF;H9J7L6N4P6\6b8f9l8np9rt8z~656"$g* 08, - 0P0202 * 02- / 0202  - 02"/ $1 (4 */ ,1 03@-/ .- 0302/ 46- 8:4 <>2 @B1 E02F02 HL6 02NP1 T/ V1 03@W/ 03X- \/ `- b/ c1 03h03pr* v* xz* |* ~6 I* 4 1 - 8 4 1 / 4 ; 7 1 7 4 1 / "4 $6 &1 (- *8 ,4 .1 0- 24 49 68 84 :1 </ >- @2 B4 D6 F* H1 J/ L* N1 P4 R6 T* V1 X/ Z* \/ `4 d* fh* 0Sj6 l1 n* pr* x01z01{01|01}01 ~010101 & - , - , 0S* )  , "- $,1 .04 246 58 0366 0381 03:/ <>- BD* F& 03H# 03J, L( 03N# 03P, R- 03T/ 03V, 03X) 03Z4 \, ^2 `1 03b/ 03d1 j4 pg6 04C|02 % 0204 * - / 1 3 03 1 / 03/  4 "$1 &- (* *1 ,.4 02/ 31 4/ @- B, D* F& H# J- L* N& P* R- T1 V/ X, Z5 \1 ^, `1 b5 d* fh* j6 ln* pr* ~9 0P08   $&*,248:<>DFJLRTXZ\^`bfhnvxz|~i .046>@NPTV^`nv| $&.046>@DFNPTV^`dfnv|  $ &* ,248:< >D FJ LR TX Z\^`dfjlv  $ &* ,248:< >D FJ LR TX Z\^`bfhnvxz|~= 0P! ! !!!$&*,248:<>D!FJ!LR!TX!Z\!^`bfhnvx!z|!~g#!! #.0 46 >@#NP!TV!^`!np ~##!! #$&#.0 46 >@#DF#NP!TV!^`!df!ln tv z| ~  %$&*,248:<>DFJLRTXZ\^`dfjlv  %$&*,248:<>DFJLRTXZ\^`bfhnvx!z|!~< 0P# # ###$&*,248:<>D#FJ#LR#TX#Z\#^`!bf hn!vx#z|#~g&%% &.0%46%>@&NP%TV%^`%np%~&&%% &$&&.0%46%>@&DF&NP%TV%^`%df%ln%tv%z|%~  *$&*,248:<>DFJLR TX Z\ ^`df!jl!v  *$&*,248:<>DFJLR TX Z\ ^`!bf hn!vx#z|#~- 0P( ( ((*$!&*!,2#48#:<!>D(FJ(LR(TX(Z\*^`%bf#hn%vx(z|*~g(( .0(46(>@NP(TV(^`*np)~(( $&.0(46(>@DFNP(TV(^`*df*ln)t! ! !!!$!&*!,2#48#:<!>D!FJ!LR#TX#Z\!^`!df#jl%v! ! !!!$!&*!,2#48#:<!>D!FJ!LR#TX#Z\!^`%bf#hn%vx(z|*~     0    BambooTracker-0.3.5/demos/modules/Underwater Ruins.btm000066400000000000000000000626551362177441300227440ustar00rootroot00000000000000BambooTrackerModeMODULE Underwater Ruins OPNA ConversionWavetable Guy/SuperJet Spade 2018-2019lMy very first "complete" PC-98 tune. This is actually a conversion of an OpenMPT song that I never uploaded.<INSTRMNT CEcho Piano Bell> Synth Bass;Strings SSG SimpleSSG Hold> Piano Bell SSG "Blow"? Bottle BlowBottle Blow Echo  Decay Short Hold+Decay ShortINSTPROPmU? ? ? ??"? ? ?C$a?`% ?U? ??  ?G3??*/%12l @  >  H   GROOVE SONG F`@` Underwater Ruins OPNA Conversion}?  @0< 50<5 0&<*5006<:5@4@ ;4@; 4&@*;046@:;@0< 50<5 0&<*5006<:5Z$$0$$$ $#$$.00$3$4=$>Z$$0$$$ $#$$.00$3$4=$>Z((4(((# (#($.40(3(4=(>#[$$0$$$ $#$$.00$3$4=$>Z$$0$$$ $#$$.00$3$4=$>Z$$0$$$ $#$$.00$3$4=$> Z((4(((# (#($.40(3(4=(># Z$$0$$$ "#"$..0"3"4="> Z  ,     # $.,0 3 4= > $$0. . +.$$0...+ $"$#&0'.*.+.+/.0'2'36)7+:+;>)?+ $$0. . +.$$0...+ $"$#&0'.*.+.+/.0'2'36)7+:+;>)?+$$0. . +.$$0...+ $"$#&0'.*.+.+/.0'2'36)7+:+;>)?+$$0. . +.$$0...+ $"$#&0'.*.+.+/.0'2'36)7+:+;>)?+""., , ),"".,,,) """#&.',*,+.)/,0%2%36'7):);>'?)""., , ),"".,,,) """#&.',*,+.)/,0%2%36'7):);>'?)Z$$0$$$ "#"$..0"3"4=">Z$$0$$$ "#"$..0"3"4=">Z  ,    '#'$.+0'3'4='>"Z  ,     # $.,0 3 4= >"`$$0$$$ $#$$.00$3$4=$>?0B y  ,3 <3<"3,<23<<,8 @8@"8,@28<@,3 <3<"3,<23<<@0< 50<5 0&<*5006<:5035< 5 <5035<5<5 0"3$5&<*5,<.50023456<:5<<>548;@ ; @;48;@;@; 4"8$;&@*;,@.;04284;6@:;<@>;035< 5 <5035<5<5 0"3$5&<*5,<.50023456<:5<<>5035< 5 <5035<5<5 0"3$5&<*5,<.50023456<:5<<>5035< 5 <5035<5<5 0"3$5&<*5,<.50023456<:5<<>5 48;@ ; @;48;@;@; 4"8$;&@*;,@.;04284;6@:;<@>; 035< 5 <5035<5<5 ."1$3&:*3,:.30.21436::3<:>3 ,038 3 83,038383 ,"0$3&8*3,8.30,204368:3<8>3 <?C < ?C<?C?CFH F"C$?&044+.<04/?0<2:4760448H04FC C F??<?CFCF H"F$H&044+.K04/H0F2C4<60448F04DA ? AD?=:5 ="$A&(D*F,I.K6044:I04FDFI  FDA?A=:= :"$8&(5*8,:.8056044800435< 5 <5035<5<5 ."1$3&:*3,:.30.21436::3<:>3035< 5 <5035<5<5 ."1$3&:*3,:.30.21436::3<:>3,038 3 83,038383 +".$3&7*3,..30.2+4'6":<>,038 3 0830,'$ $ '"$$ &$*',,.'0$2'4,60:3<.>5035< 5 <5035<5<5 0"3$5&<*5,<.50023456<:5<<>5  ,5555$5.545>5,;;;;$;.;4;>;,5555$5.545>5,3 <3<"3,<23<<5 080P00835 < 5<5035<5<!5#0%3'5)<-5/<153053759<=5?<548; @ ;@;48;@;@!;#4%8';)@-;/@1;34587;9@=;?@;0 35 < 5<5035<5<!5#0%3'5)<-5/<153053759<=5?<5035 < 5<5035<5<!5#0%3'5)<-5/<153053759<=5?<5035 < 5<5035<5<!5#0%3'5)<-5/<153053759<=5?< 548; @ ;@;48;@;@!;#4%8';)@-;/@1;34587;9@=;?@ ;035 < 5<5035<5<!5#.%1'3):-3/:133.51739:=3?: 3,03 8 383,03838!3#,%0'3)8-3/8133,507398=3?8 A+0P08++ +"0+23+5 :+++ +"0+23+5:+++ +"0+23+5:+++ +"0+23+5:))) )"0)23)5:))) )"0)23)50 080P35 < 5<5035<5<!5#.%1'3):-3/:133.51739:=3?:3035 < 5<5035<5<!5#.%1'3):-3/:133.51739:=3?:3,03 8 383,03838!3#+%.'3)7-3/.133.5+7'9"=?,03 8 30830,'$ !$#'%$' )$-'/,1'3$5'7,90=3?.5035 < 5<5035<5<!5#0%3'5)<-5/<153053759<=5?<' G0 080P < 50<5#0)<-5309<=5@4 @ ;4@;#4)@-;349@=;@0 < 50<5#0)<-5309<=535080P555$5.545>5m008           4 0  0 4 0 . ],  ! " #$%&'()*+,-./01234 >. 08.. ."0.23.5 :... ."0.23.5:... ."0.23.5:... ."0.23.5o5$#"!        485480 08 .0 ., +],  ! " #$%&'()*+,-./01234 0 d 03 0P<3<%3/<53?<,8@8@%8/@58?@,3<3<%3/<53?<N0 085 553<<#0'5-515539<=5p3!0P08            8 3  3 8 3 1 ]0  !"#$%&'()*+,-./0123 4 ;0 00 0"002325 :000 0"002325:000 0"002325:000 0"002325o=$#"!        4<=4<?o3$#"!        13 10 .]0  !"#$%&'()*+,-./0123 4 3  .5 080P55!5'51575,5;;;!;';1;7;,;555!5'51575N53 <<055!5%3)</<30575?<p8080P           ; 8  8 ; 8 6 ]3  ! " #$%&'()*+,-./01234 >30833 3"032355 :333 3"032355:333 3"032355:333 3"032355:111 1"012335:111 1"0123358 08 68 63 3]3  ! " #$%&'()*+,-./01234 8  ))) ' ),...,.0 3$3(3*0,3.00.40478 9 : ;<=>?04 /,/ , (,(( #%( ,$/(1*4,8.;0440478 9 : ;<=>?04 }0,) ' ),'$'$'$ "$%("*%,".%0,4.8,:*<'>% ,',03 0 38383030 ,"047'( ) * + , -./01234504 $')0 ) 0)$')0)0) $"'$)&0*),0.)0$2'4)60:)<0>) $')0 ) 0)$')0)0) $"'$)&0*),0.)0$2'4)60:)<0>)$')0 ) 0)$')0)0) $"'$)&0*),0.)0$2'4)60:)<0>)$')0 ) 0)$')0)0) $"'$)&0*),0.)0$2'4)60:)<0>) ") % )% ")%)% " $"&)*%,).%02 4"6):%<)>% ") % )% ")%)% " $"&)*%,).%02 4"6):%<)>%m$ $ $ $ $ $ $ $ $ $ "" %" (" +" ." 2" 5" 8" ;" >" h$ $ $ $ $ $ $ $ $ $ "" %" (" +" ." 2" 5" 8" ;" >" h          " % ( + . 2 5 8 ; > ^          " % ( + . 2 5 8 ))) ' ),...,.0 3$3(3*0,3.00.40478 9 : ;<=>?04f {) 0P|) ) '),...,.!0#3'3+3-0/3103.; < =>? / , / ,(,(( #%!(#,'/+1-4/81;34; < =>? 0 , ) '),'$'$'!$#"'%+"-%/"1%3,7.;,=*?' %,', 0 3 03838303!0#,* + , -./02467800P|3 7  03703737:!<#:%7'3)044.100423305.7+9044;<04: 7  7:33037:7!:#<%:'<)044.1?042<3:57709044;:048  5 35831.)!#1%'5)+8-:/=1?9044==04:8 : = :85351.!1#.%',)+)-,/.1,3)9044;l' 04' ' ' ' ' ' ' ' ' "% %% (% +% .% 2% 5% 8% ;% >% h' ' ' ' ' ' ' ' ' ' "% %% (% +% .% 2% 5% 8% ;% >% h$ $ $ $ $ $ $ $ $ $ "" %" (" +" ." 2" 5" 8" ;" >" ^$ $ $ $ $ $ $ $ $ $ "$ %$ ($ +$ .$ 2$ 5$ 8$ {) 0P|) ) '),...,.!0#3'3+3-0/3103.; < =>? )0P)) ' ),...,.0 3$3(3*0,3.00.40478 9 : ;<=>?04 /,/ , (,(( #%( ,$/(1*4,8.;0440478 9 : ;<=>?04 }0,) ' ),'$'$'$ "$%("*%,".%0,4.8,:*<'>% ,',03 0 38383030 ,"047'( ) * + , -./01234504 $ 0P') 0 )0)$')0)0!)#$%''))0-)/01)3$5'7)90=)?0 )$') 0 )0)$')0)0!)#$%''))0-)/01)3$5'7)90=)?0)$') 0 )0)$')0)0!)#$%''))0-)/01)3$5'7)90=)?0)$') 0 )0)$')0)0!)#$%''))0-)/01)3$5'7)90=)?0) " ) %)% ")%)!%#% '"))-%/)1%35 7"9)=%?)) " ) %)% ")%)!%#% '"))-%/)1%35 7"9)=%?)q%, 0P, , , , , , , , , "* %* (* +* .* 2* 5* 8* ;* >* h, , , , , , , , , , "* %* (* +* .* 2* 5* 8* ;* >* h' ' ' ' ' ' ' ' ' ' "' %' (' +' .' 2' 5' 8' ;' >' ^' ' ' ' ' ' ' ' ' ' "' %' (' +' .' 2' 5' 8' )0P)) ' ),...,.0 3$3(3*0,3.00.40478 9 : ;<=>?04 @ S$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$ T$$$ $$$$$ $$$($,$0$4$8$<$ T$$$ $$$$$ $$$($,$0$4$8$<$ T$$$ $$$$$ $$$($,$0$4$8$<$ T$$$ $$$$$ $$$($,$0$4$8$<$ T$$$ $$$$$ $$$($,$0$4$8$<$t$$$$ $ $$$$$$$ $$$'$($*$,$0$4$7$8$:$<$t$$$$ $ $$$$$$$ $$$'$($*$,$0$4$7$8$:$<$t$$$$ $ $$$$$$$ $$$'$($*$,$0$4$7$8$:$<$t$$$$ $ $$$$$$$ $$$'$($*$,$0$4$7$8$:$<$T$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$T$$$ $$$$$ $$$($,$0$4$8$<$  $$ $$$$$,$4$<$3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$ $$ $$$$$,$4$<$ $$ $$$$$,$4$<$ 3$ $$$$$,$4$<$=$>$?$ $$ $$$$$,$4$<$ 3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$3$ $$$$$,$4$<$=$>$?$$$ $$$$$,$4$<$ U  00 /00?0 00 /00 00 /00?0 $00 /00?0 $00 /00?0 00 /00 00 /00?0 00 /00 00 /00?0 00 /00 00 /00?0 00 /00 00 /00?0 00 /00 00 /00?0 00 /00 00 /00?0 m  $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$ $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$ $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$ $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08 $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$ $08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08$08$08$08 $08 $08$$08$08$08$08$08$"$08#$08&$08)$08+$08.$2$083$086$089$08;$08>$$08$08$08 $08 $08$$08$08$08$08$08$"$08#$08&$08)$08+$08.$2$083$086$089$08;$08$08$08$08 $08 $08$$08$08$08$08$08$"$08#$08&$08)$08+$08.$2$083$086$089$08;$08>$$08$08$08 $08 $08$$08$08$08$08$08$"$08#$08&$08)$08+$08.$2$083$086$089$08;$08$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08$08$08$08$08 $08 $08$$$08$08$08$08$08$"$#$08&$08'$08)$08*$08.$2$3$086$087$089$08:$08>$  ,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$ ,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$,$ $$$$$,$4$<$BambooTracker-0.3.5/demos/modules/breeze 2608.btm000066400000000000000000000772631362177441300214000ustar00rootroot00000000000000BambooTrackerMod~MODULE + breeze 2608maak<INSTRMNT= slap bass;d.piano;e.pianokicksnarehat:melody ssg delayseq. ;p.synth @ d.squarewave symbal open tom@ synth lead 1 buzzer(saw)rev.sym  clap+reverb :bass 2> synth slowINSTPROPW 7? E,?G ? G'? HG?#7 H?d7 eHG:O28L8:O#8L8G? 9 ?   F? `7/` ? 7/ G9 ` 4 ` 9 4 74 ?g?? 0?,?G ? G'? ((-(&()0 1  -         !  2 T $  >          $ ,               }      3, 0 0 00, 01/-,+**)GROOVE SONG tt  %% 0 %% $&$(*.,.$4$8<'>(03@@)03F)HJ3LN)T)X`,f,hj,p v xz %% 0 %%" $&$(*.,.$04$8'<(030@)03F)HJ3LN)PT)X\`,df,lp tv |B""-.03 03)@.D.HT'\)/% % $,$@)03L)`,l $ ) +& $&+(*$.&:<@F$HJLNPT+Z\"` f'hj lnt&z|+%% 0 %%" $&$(*.,.$04$8'<(030@)03F)HJ3LN)PT)X\'^(03 `)03df)lpt'v)03 x03z'|)%, % $+" $&+(*$,.&24!:<@F$HJ)LNPT+Z\"` f'hj,ln"pr's)03 x$03z|#02#0S#0S %%0S%0S8</>103@?03@#H#0SL%T%0SX%0S\h(0Slpt*vx|(~*03@03##0S#0S %%0S%0S8</>103@?03@#H#0SL%T%0SX%0S\h#0Slp#r%03 t03x &%0S&#$0S %$&%(4%8(<)030@*03D(0SF*HX\b-df!0Sh-l#0Sn!rt!vx-z 0S|&~% &0S&  &# %$0S&%,%<@*D*0SFL*\,`-d-0Sf(l!t!x-0Sz&& 1 && %&%(*/,.%4%8<(>)03@@*03F*HJ4LN*T*X`-f-hj-p!v!xz!&& 1 && %&%(*/,.%4%8<(>)03@@*03F*HJ4LN*T*X`-f-hj-p!v!x& %@*`!& %@*`-p!# %@*`+x&& 1 &&# %&%(*/,.%04%8(<)030@*03F*HJ4LN*PT*X\`-df-lp!tv!|&& 1 &&# %&%(*/,.%04%8(<)030@*03F*HJ4LN*PT*X\`-df-lp!tv!|#03# - ##$ %&%(*/,.%04%8(<)030@*03F*HJ4LN*PT*X\(^)03 `*03df*lpt(v*03 x03|020~& %@ ?0C## - ##$ %&%(*/,.%04%8(<)030@*03F*HJ4LN*PT*X\`-df-lp!tv!|&%0S&#$0S %$&%(4%8(<)030@*03D(0SF*HX\b-df!0Sh-l#0Sn!rt!vx!|"## - ##$ %&%(*/,.%04%8(<)030@*03F*HJ4LN*PT*X\`-df-lp!tv!|=    M  (,<@HL\`hl| $&,026<@HNPT\`dflprv|F$$$"8.@$D$HPT"X\$! 08  @ ` p  08  @ ` M  (,<@HL\`hl|w  !! &(,.!<@ F HN!PT!\`"f"hn"|08  &(,.!<@ F HN!PT!\`"f"hn p|4&08 (03 @&03L(03 \&`03| ?&08 (03@&03L(03\&03p(03 r03x g08 $&(@ DF Hbdhlnrt|~ m   $&*, <@!DF L!\`dflt|  08  @  P08  (,<@ HL \`hl|P08  (,<@ HL \`hlx*08 *@,`+!*08 *@,`+p+ *08 *@,`*x08 $&,026<@ HN PT \`dflprv|! 08  @ ` p   08  @ ` x4 !(!4!@ \!h!t!    P 08   "(,"<@"HL"\`"hl"| 08   "$&",0"26"<@"HN"PT"\`"df"lp"rv"|F%%%'43@%D%HPT%X\' 08  @ `  08  @ ` P 08   "(,"<@"HL"\`"hl"|$$ $ "" "&"(*,.$<@$F$HJ$LN"PT"\`$f$hjln&| 08  " "" "&"(*,.$<@$F$HJ$LN"PT"\`$f$hj$ln$pr$vx$y|$}4*08 ,03 @*03L,03 \*`03| ?*08 ,03@*03L,03\*03p,03 r03x g!08! #$&#(@#DF#Hb#dh#ln!rt#|#~ m!!  ! #$&#*,#<@#DF#L#\`#df#l#t!| ! 08  @  P!08 ! #(,#<@#HL#\`#hl#|P!08 ! #(,#<@#HL#\`#hl#x-08 /@/`/!-08 /@/`/p# -08 /@/`/x!08!! #$&#,0#26#<@#HN#PT#\`#df#lp#rv#|! 08  @ `!  ! 08  @ ` x4 "("4"@ \$h$t$S    P$08 $ &(,&<@&HL&\`%hl%|$08$$ &$&&,0&26&<@&HN&PT&\`%df%lp%rv%|F)))+,7@)D)HPT)X\) 08 " @" `"  08  @ ` P$08 $ &(,&<@&HL&\`%hl%|w''&& &&&(,.(<@'F'HN&PT&\`'f'hn)|$08$ && &&&(,.(<@'F'HN&PT&\`'f'hn'pr'vx'y|'}4-08 -03 @-03L-03 \-`03| ?-08 -03@-03L-03\-03p/03 r03x g%08% '$&'(@'DF'Hb&dh&ln%rt&|%~ m%#  % '$&'*,'<@%DF%L%\`&df&l&t%|  08 # @#  P%08 % '(,'<@'HL'\`&hl&|P%08 % '(,'<@'HL'\`&hl&x%08 '@'`&!%08 '@'`&p& %08 '@'`&x%08%% '$&',0'26'<@'HN'PT'\`&df&lp&rv&| 08 # @# `#   08  @ ` x4 &(&4&@ \(h(t(    M' ' '(,'<@'HL'\`'hl'|''' '$&',0'26'<@'HN'PT'\`'df'lp'rv'|K,,,,(8<,@,D,HPT,X\,?, 083 08, 08. 08 3 08 0 08. 08+ 080 08, 08+ 08' 08, 083 08' 08 + 08"3 08$3 08&+ 08(. 08*3 08,0 08.. 080+ 0820 084, 086+ 088' 08:, 08<3 08>' 08@. 08B3 08D0 08F. 08H, 08J0 08L0 08N, 08P+ 08R0 08T, 08V+ 08X' 08Z, 08\3 08^' 08`, 08b3 08d3 08f, 08h. 08j3 08l0 08n. 08p' 08r0 08t) 08v' 08x$ 08z) 08|0 08~$ 08M' ' '(,'<@'HL'\`'hl'|w &(,.<@FHNPT\`fhn!|'08'  &(,.<@FHNPT)\`fhnpr+vx)y|)}?$08 "03@$03L"03\$03p&03 r03x4%08 #03 @%03L#03 \%`03| ?%08 #03@%03L#03\%03p'03 r03x g(08( ($&((@(DF(Hb(dh(ln(rt(|(~ m((  ( ($&(*,(<@(DF(L(\`(df(l(t(|   P(08 ( ((,(<@(HL(\`(hl(|P(08 ( ((,(<@(HL(\`(hl(x(08 (@(`(!(08 (@(`(p( (08 (@(`(x(08(( ($&(,0(26(<@(HN(PT(\`(df(lp(rv(|?- 084 08- 08/ 08 4 08 1 08/ 08, 081 08- 08, 08( 08- 084 08( 08 , 08"4 08$4 08&, 08(/ 08*4 08,1 08./ 080, 0821 084- 086, 088( 08:- 08<4 08>( 08@/ 08B4 08D1 08F/ 08H- 08J1 08L1 08N- 08P, 08R1 08T- 08V, 08X( 08Z- 08\4 08^( 08`- 08b4 08d4 08f- 08h/ 08j4 08l1 08n/ 08p( 08r1 08t* 08v( 08x% 08z* 08|1 08~% 08- 084 08- 08/ 08 4 08 1 08/ 08, 081 08- 08, 08( 08- 084 08( 08 , 08"4 08$4 08&, 08(/ 08*4 08,1 08./ 080, 0821 084- 086, 088( 08:- 08<4 08>( 08@/ 08B4 08D1 08F/ 08H- 08J1 08L1 08N- 08P, 08R1 08T- 08V, 08X( 08Z- 08\4 08^( 08`- 08b4 08d4 08f- 08h/ 08j4 08l1 08n/ 08p( 08r1 08t* 08v( 08x4 )()4)@ \)h)t)  "$"')'+ ' $"$"&'&$" !$"'#$$'%$&''$('))+'01$2'3$4'5$6'7)8+9,;+=)>'u' `0430c)048503`:03@3L`043Xc504Y703 Z03d1e003 f03jl.t0vx3|0`043c,04 )`043*,c)040+4,03B5038+@'BD$L'M)03 N03T`043dhc304p020t502|0200230  ,.003 03,"$,(+0'8)03 :03@2A303 B03H0JL2M303 N03T5\.d0h3jl,rt'x)|,}.03 ~03,,043,0c004418,:<.D,FH,T`043Xc'04Y)03 Z03\'03@^03h`043x|`04+,03 03 1 303 038:03 803 7$803 %03&(:*,3.`0434c7048<5>703 D503@L703T803 U03V`043Z\c104]303@^03bd-e.03@f03hl,pr'st+x,03 y03|', + ,03 x03043{)03 0403)+03 03$,(+,0'46$<)=+03 D`043ltx0304+,03 03 , .03 02,3503 03 7$3&(0*,3002400283<503 =03@5B703 H8L703T803 U03Z\8]:03 d8f03h:i<03@k03p`043 c+04,03 03 1 303 x03043c704803 038 7$803 %03&(7,3.`0434c5048<.=003 D.03@L003T5X02 \{10204]303@^03d,e.03 f03hl,pr'st+x,03 y03|3 |803 7 303303 503 0<,03>.@.A003 H+03L,03PT,U.X\'x >{20304303@503@303 8703@<3X5\3 a045020 02 002002.003 03.003 020"02$0(020*02,)-+03 4`0438<{)0304>+03 D.03H`043Lc$04P. T"X$\)^+b$ d203e303 h2l+03t`043|a04-4603 03 4,`0438c604<403 =03H`043TXc204Y403 Z03bd2h1jl/vx2y403 z03|- `043c(04* *!,03 "03$-03 %03(,*,(0`04D4c%0468/<-03 =03H`043Tc-04VX,\4h`043p020t{60204|02002xc(04 `0430c*048603:03@4L`043Xc604Y803 Z03d2e103 f03jl/t1vx4|1`043c-04 *`043*,c*040,4-03B5038,@(BD%L(M*03 N03T`043dhc404p020t602|0200241  -/103 03-!$-(,0(8*03 :03@3A403 B03H1JL3M403 N03T6\/bd1h4jl-rt(x*03 y03|-}/03 ~03--`043,0c1044203 5038-:</D-FH-T`043Xc(04Y*03 Z03\(03@^03h`043x`04,-03 03 2 403 039;03 903 8$903 %03&(;*,4.`0434c8048<6>803 D603@L803T903 U03V`043Z\c204]403@^03bd.e/03@f03hl-pr(st,x-03 y03|(,-03 03 - /03 02-4603 03 8$4&(1*,4002410284<603 =03@6B803 H9L803T903 U03Z\9];03 d9f03h;i=03@k03p`043 c,04-03 03 2 403 x03043c804903 039 8$903 %03&(8,4.`0434c6048</=103 D/03@L103T6X02 \{20204]403@^03d-e/03 f03hl-pr(st,x-03 y03|4- , -03 x03043{*03 0403*,03 03$-(,,0(46%<*=,03 D`043tx0304z8903 03 803 !03$4(1,/8`043Tc(04X*\0]103 ^03l`043xc404|603 }038903 036= 903@ 03`04348c804<9>@5A603 D4H103L4T6X\6l`043c304403 6$103(403 ,(038-<,03 =03H`043Tc,04X-\3]403 ^03l`043xc904|8~64 4`0434c104684<603 =03@1D-GH*L-PT6X\2]403`03l`043x`04- , -03 x03043{*03 0403*,03 03$-(,,0(46*<*=,03 D`043\{(0304t-u/03 v03r- , -03 x03043{*03 0403*,03 03$-(,,0(46%<#D`043    " 08$"' ) ' + '$" $"& '& $!"#$$%'&$'' ($)' *$+',).'34$5'6$7' 8$9':);+<,>+@+s' 0P`0438c)04@503`B03H3T`043`c504a703 b03l1m003 n03rt.|0~30 `043c,04) `04324c)048+<,03B=03@+H'JL$T'U)03 V03\`043lpc304x020|5020200230,.003 03$,*,,0+8'@)03 B03H2I303 J03P0RT2U303 V03\5d.l0p3rt,z|'),.03 03 ,,04348c004<1@,BD.L,NP,\`043`c'04a)03 b03d'03@f03p`043a04 + ,03 031303 038:03 $8&03(7,803 -03.0:2436`043$D)E+03 L`043t|x0304,03 03' +,03 03,.03 02, 3$503 %03(7,3.00243802<002@3D503 E03H5J703 P8T703\803 ]03bd8e:03 l8n03p:q<03@s03x`043 c+04 ,03 031303 x03043c704803 03#$8&(7,803 -03.07436`043@/D-03 E03P`043\c-04^`,d4p`043x020|{60204v( 0P040438c*04@603B03H4T`043`c604a803 b03l2m103 n03rt/|1~41 `043c-04* `04324c*048,<-03B=03@,H(JL%T(U*03 V03\`043lpc404x020|6020200241-/103 03$-),-0,8(@*03 B03H3I403 J03P1RT3U403 V03\6d/jl1p4rt-z|(*03 03-/03 03 --`04348c104<203 =03@-BD/L-NP-\`043`c(04a*03 b03d(03@f03p`043`04 , -03 032403 039;03 $9&03(8,903 -03.0;2446`043%D*E,03 L`043|x030402 3 7 :<0 237:<0 2!3#7%:'<)0 +2-3/71:3<507293;7=:?<A0C2E3G7I:K<M0O2Q3S7U:W<Yg$ $, %-03 &03(,03 )03,(0%4#@`043\c04`d$e%03 f03t`043c(04*03 03, -03 03 *1-03@03$`043<@c,04D-F H) I*03 L(P%03T(\*` d* t`043t$c'04%(03 (*,%030(03 403@!D 03 E03P`043\c 04`!d'e(03 f03t`043c-04, * ( ( $`043@(D*03 E03H%L!OPT!X \* ` d& e(03h03t`043-03 034 -,-03 x03043{*03 0403$*%,03 &03,-0,48(<>*D*E,03 L`043d{(0304|-}/03 ~03r -,-03 x03043{*03 0403$*%,03 &03,-0,48(<>%D#L`043   0000 00000000 0$0&0(0,0.000406080<0>0@0D0F0H0L0N0P0T0V0X0\0^0`0d0f0h0l0n0p0t0x0|00000 00000000 0$0&0(0*0,0004060:0;0<0>0@0D0F0H0L0N0P0T0V0X0\0^0`0d0f0h0j0l0p0t0v0z0{0|0~00000 00000000 0$0&0(0,0.000406080<0>0@0D0F0H0L0N0P0T0V0X0\0^0`0d0f0h0l0n0p0t0v0x0|0~0000 00 000 0$0(0,! .! 0! 4 6 8 < > @0D0H0L0P0T0X0\0`0 d0h0 l0p0 t0v0x0 |000 0 0 00 00 0$0 (0,0 0040 80<0>0 @0D0 H0L0 P0T0 X0\0 `0d0 h0l0 p0t0 x0|0~0 0000 000000" " 0$0&0(0,0.000406080< > @0D0F0H0L0N0P0T0V0X0\" ^" `0d0f0h0l0n0p0r" t" v0x | ~ 89=@D E 89=@DE8 9=@ D"E$8 &9(=*@,D.E08 294=6@8D:E<8>9@=B@DDFEH8J9L=N@PDRET8V9X=Z@\D^E`8b9d=f@hDjEl89=@D E 89=@DE8 9=@ D"E$8 &9(=*@,D.E08 294=6@8D:E<8>9@=B@DDFEH8J9L=N@P0w 0 -0P~*- 4 *4 -*- 4*4 ,$*&, (4,*.4 0,4(6, 84<(>4 @4D1F4 H8L1N8 P4T1V4 X8\1^8 `4d/f4 h6l/n6 p4t-v4 x6|-~6 0237: < 0 237:<0 237 :"<$0 &2(3*7,:.<002243678::<<0>2@3B7D:F<H0J2L3N7P:R<T <  ,0P~), 3 )3 ,), 3)3 +$)&+ (3,).3 0+4'6+ 83<'>3 @3D0F3 H7L0N7 P3T0V3 X7\0^7 `3d.f3 h5l.n5 p3t,v3 x5|,~5 )))W.0P050 0 :5: 05 .0 ).0 .$0&5(0 *0,:.50: 2045 6.80 :)<$>@.D0F5H0 J0L:N5P: R0T5 V.X0 Z)\.^0`.d0f5h0 j0l:n5p: r0t5 v.x0 z)|$~00 0 000 0 0$0(00020 80<0>0 @0H0L0P0R0 X0\0`0d0h0p0r0 x0z0 |0~0 000 0 000 0 0$0(00020 80<0 @0A0 H0L0P0R0 X0\0`0d0h0p0r0 x0z0 |0~0 000 0 000 0 0$0(00020 80<0 @0A0 H0L0P0R0 X0\0`0d0h0p0r0 x00'0D 000 0 00000 000 0$0&0 (0,0.0004060 80<0>0 @0D0F0 H0L0N0P0T0V0 X0\0^0 `0d0f0 h0l0n0p0t0v0 x0|0~0 000 0 00000 000 0$0&0 (0,0.0004060 80<0>0 @0D0F0 H0L0N0P0T0V0 X0\0^0 `0d0f0 h0l0n0p0t0v0 x0 0 H0x0000 0 00000 000 0$0&0(0*0 ,0004060:0;0 <0>0@0D0F0 H0L0N0P0T0V0 X0\0^0`0d0f0h0j0 l0p0t0v0z0{0 |0~0 000 0 00000 000 0$0&0 (0,0.0004060 80<0>0 @0D0F0 H0L0N0P0T0V0 X0\0^0 `0d0f0 h0l0n0p0t0v0x0|0~000 0 0 000 00 0 0$0 (0,0 .00040 80<0 >0@0D0 H0L0 N0P0T0 X0\0 ^0`0d0 h0l0 n0p0t0 x0|0 00 0 0 000 00 0 0$0 (0,0 .00040 80<0 >0@0D0 H0L0 N0P0T0 X0\0 ^0`0d0 h0l0 n0p0t0 x0z0 000 0 00000 000 0$0&0 (0,0.0004060 80<0>0 @0D0F0 H0L0N0P0T0V0 X0\0^0 `0a0 h0l0m0 t0x0y0 |0~0 0000 00 0 0$0&0(0 ,0 0020 40 80:0 <0 @0A0 D0E0 H0J0 L0N0R0 T0V0X Z \ ^ `0a0 h0i0 n0o0p0r0 t0v0x0z0|0~00000 0 0000000 0$0&0(0,0.000204080<0>0@0D0F0H0L0 N0P0R0T0X0\0^0_0`0b0d0f0h0l0n0p0r0t0x0|0~0  0 00 0 0 000 00 0 0$0 (0,0 .00040 80<0 >0@0D0 H0L0 N0P0T0 X0\0 ^0`0d0 h0l0 n0p0t0 x0|0~0 00 0 0 000 00 0 0$0 (0,0 .00040 80<0 >0@0D0 H0L0 N0P0T0 X0\0 ^0`0d0 h0l0 n0p0t0x0|0~0  )00 0.0@0N0`0p0|0E0000 0&00060@0N0T0\0`0f0p0v0)00 0.0@0N0`0n0p0%00 0.0@0N0`0n0%00 0.0@0N0`0n0E0000 0&00060@0N0T0\0`0f0p0v0)00 0.0@0N0`0n0p0%00 000@0P0`0p00 0(0@0L0h0,00 0.0@0N0`0l0x0|0,0@0D0N0R0T0`0h0n0t0000 0&0,040@0T0d0f0x00-00 000@0P0`0n0p0|0)00 000@0P0`0h0p0%00 000@0P0`0p0  /0800(080H0X0h0n0x0+0800,0<0H0X0l0|070800(080H0X0h0t0x0|0~0+0800(080H0X0h0x0'0800(080H0X0h0+0800,0<0H0X0l0|070800(080H0X0h0t0x0|0~0+0800(080H0X0h0x0 0$000P0\0d0p0$00(080H0X0h0t01000 0 0&0H0x0z0~0,00(080H0X0h0l0t0|0/0800(080H0X0h0x0~0'0800(080H0X0x070800(080H0X0h0t0x0|0~0 $$$$,0 000D0L0T0\0%008000 0(0  K08$ $$$$$,$4$<$D$L$T$\$d$l$t$|$K080 000$0,040<0D0L0T0\0d0l0t0|0G080 000$0,040<0D0L0T0\0d0l0t0T0000 0(00080@0H0P0X0`0h0p0x0O0000 0(00080@0H0P0X0`0h0p0;080 000$0,040<0D0L0T0\0 & / x0BambooTracker-0.3.5/img/000077500000000000000000000000001362177441300150165ustar00rootroot00000000000000BambooTracker-0.3.5/img/icon.png000066400000000000000000000026331362177441300164600ustar00rootroot00000000000000PNG  IHDR szzsRGBgAMA a pHYs(JtEXtSoftwarepaint.net 4.0.10  IDATXGV}L[Uھ/HS@gP33b !13[ f[c2a"Ӱ,:6`C$PJ羖ҎB_s^瞏w[X8%777:b!33& . 0EQ塥?ԏ4hI4HդwPe34Tr,D{)j#ykN,3vm}=‚Y4P0j1Ʈ\6Y&.Ezl9e"&PվP"^36bBV@"Rp#$Zy$s{ Eٴd8K]O@mAóAN<ֶIpS:(*'i#>E!b 7z_2Att£oQײPvuV:Hu!<>&nԡ GQZ*֧̰Q-T)N{y!$5]KD*]5*u4ψ$DQdJxu О@;>[E)9[Z7$zxZEwϳ+@¿軮(礟 0vhp''혭X% _\K0N>.N״L>{UyDs^;V+ED16* ,"PaZLDpdV$#S@Ghdٜ, Jf344ṔC| R a+c%`RY1J|"-FIENDB`BambooTracker-0.3.5/img/overview.gif000066400000000000000000113565171362177441300173760ustar00rootroot00000000000000GIF89aS C37!!!''(5(((5543!9 @!!A !A'E,J(F(E!(E011666<<:?B9==;<;<;;<47Q1;b;8d;@?@ACA=:{CwQ^zui[RI0+8ZSp٤Ξ3ٽ J* rWrũP96gJ&+eZ/hN+paljɰif[d|hXyXlV.Ln-K(}Z -)Qin&֊clIBЃw0Lj?&qřx ârȮG%4,⚧IqVhͅkE+WpF-PEC>ʳrv3Җ2b(*-Rp,.);z*,}p #Лds͌7"ggB{RDfILwJ~)Q^c{ޤ|rRanwP/j8==߷8G/=bW hUW3n/tR#{u; uқnÏum ``2eh >Y!kLO9 ^iz %S { ڸ4ꕨ 50 Ǿڱy!9OIpH) Jp !:bb@}8!mv82d\bU`P5QN,x̣Zp&3J q2$ӉF:k$ )0G?7A Nz=\{HQ\L*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:vgK1F&fbω*6|XB (%ر!z uR{k_oz_GѮ`iTRkzZ"te ucSpu2vj:4rX;^NnG+j!s @8մ&TOQ,1ygo8!j 뾻p\l#FRv)@~<8Ul2,]YqwM6l]I{{(W#5d0:c t++o0uz5lևU0HEoDiC*G~ vDdԟ Uk3YŠ똡M2iP+V n["zm|o r=Yۭ\ ެ;˲3OE IEEz+{5GCƖP! :Ek*xduw3ǥTqry5(- `Q4l}m*䑳эS~dUAkeDמN{Sl{8Vr]xϻﹼOO;񐏼'O[ϼ7{GOқOWx'fOϽwOO;=O[Ͼ{Oߗ>Oo8Xxp~8 xW8ׁ g$h}*|-ǧ~[@ h~w@B8DXFxHЃ0X|9;MpX}Pw @b8b( '৆0tXc|p z{j 8x g{HhȆMȄsXG Љ<>8 x؊ç(R؄ NxQP [8} 9TpUPP G w{8{ f@{{Q@ v{0 [p ʸ{j U@TPI pY {0P| 'P u0{o P?(א0Ȓw\ 0f: cPFyEiXؔNP8h{hZ9Jً[y])N@SpY0gp| ؐP9907 ݘ{WH Ҡ>Y|`"{s G 8W{@ }`60}ؘS 錬隰) 9|y 0{ { ٜɜ@ H|Y{נ {(ٝ׀{.IY i{p Հ{ xw eٟ9L:: SY;8 Zc|I٠$8Ri[S@{ &j*x{ g7@P{y@{~@`P {g@ 8)G ~p҈MXw BjYX y {@ 腱ɦq |hp W vP n@)y{ˉ z8 Л)ѹyÇչp w %Y 7 Ъ@P j{0f d MEz{4hwd‰fpǺȊJ{P(Ƿٺ[签g}ƺʺf`{犬麬zjkyТW HPHPk { 7p2*9@@:j{lH A s P Ԑ`t( QʱG  }YZ!*~@ u('. Fk*I{k ) PRplQ;U{ z7  z 'uYK;{  aRz{I z멠굹GZ߹{ڪ۪*Z{`?j Zڮg0g;*k @ۊZۭ׹׹ۭz"EpH k;ǻ;& !˼NSMG M [N; G {8U [{l8Ф F {gB* E ٸ' 0XQ숌('Hk, Z{kZ릒pTsJPk8þ aK|B| W7[0 @ ) Y*| +{{){ wK{` `P `' Z"ŪۺOɮȫxǻ|ɿ˭ܻ7+[yqȣī{k{KȦgɓ³G P쫰k@ {gӀh@* ˰NzΌ{8 ex5yyC„YP,L <<;>A| '{r[):{ P\0P@j@Ź | pGy ۝${ƭ* 0  o\6P Pϒ: W*ښԜZ99T=թϺȮ +ɴ۔ɴOm+تKK֪p հ U׌`˺+eViぼ~MՀ]kW rN7 {>{ < zѹwP~Yj~TM`Ϸ'W(YS@ΣʹT5 X={ @{ũP 0 ۴w S0:8mާ *{ 䀐+{]\YӉK5= mgΤ (L EeM'2ȋ؈쬌,`*.*\0)2c}nჭmoB.> #ng}x}&L;{PKMmP޷gZߎ8M{}D{0ڵ ܵЖy! հ} :~ DPǫpsMص'ʃX⪫4ȑ11_.7n>Q^x{_9CؐQ淧p`4/op١ͳg<{ ' džЙeУN{٥]۸w C@ z-[ wQ.{p.|w-M| T` ; 5ƵGn3{η @>  ` @  [}?3wY OOo l:ouL_أǽIcnjcVM2VFMnqhR2?l٧ I5PD҄c4jFRWyL Q܍GU.jO0zNNjWt`SU!_-=r4j>VSIt\;T8=ÆM]6yKpEYh'a5K,Zk_㓤>UYjҚ53IB(]0.o]t\|_>-izo|߇/MЫ9NLN<{ ,pB |Ⱥ6C<+< L:4,B9ppM,GwQ1ɩ '&$ZV%Zܠ$r-2p$POl@2Acsa91yf i2jf9CBZ }B(D9HΩ0=JM7T-2T~^Zh`| Uz~+Qurj/BIyhđ!4㑜(L m4*4XVubY6HYO@!LQn% !jC.P<:\D36C<1cO>?*QRK~"t䔧Se eXlo6ad,yGrHL$ZԨ'94ʁLA;mfAy&45zfPj͆\af7fEo'G=:ҽI QRDQo3ɄOCC=B㗶d-tG[IѕZZ=ZtY`%AEUwe5PVp!gYˁvpVl=2Xdy`!7O1)d;ьǞtYƣz.( p cz&'bs @'x65iQ"GCa X@i% Qf|! uéQ͇O9 +b*"qU'%.y  IlfԉN̰RHoF.Oc`DAch2c\$7 ds4L.uYVϗGBfԏ0S<!!K(CК.! vӛg8AP 5gX^ٔ:׉vS9Dg>{TF0'Q NĠ98jiۜ4Qg$@Dl0(>BTdQBJU:|Y" T*p%+I`*әVAb?`zX$@j&2U\(Cz8U%6OQ+?>Dg0 yq#yғwk^mӯ_9W¢^|bV=_l5<ȿkCmhE ZqD!cIe73$.۽ZƳ%G:1\{Ԥm<~òpi\k8}O|&o8#!GeVe zNɄd:yA8t{S6so@a/)Gʚ^uAT:Qi{ٜrO(A;=B8:$xB$۾n{>+;|_xK{1zҏjN/}e?{}u{}Md5=槮q=.}#VgZx~?{[*;?e*~/?>@?ÿCC@)l9 39SH<8 >S㾾۹¢A4?<>K @4?I?"̼ʓ B! ;Ѿ! s D}B*#!Bc8BAB¸7 C6?,!>,9/F,3G3ITD]KK5K5N7OZP5PD;S$XU5U5Xt7Yt@kZ5ZETL:]=_5_F^9b;Œbftghijklmnoǒ3`CFtQudvtwxyz{|}GwdeDȁ ȃDH4Ȅdȃ\ȆHzȈlȉȄȋtHHdȌȀI)s54tǖtɗɘəɚ7ȑ IHɐDHHHHHH,8r6,7ɪʫʬʙGccIHHH,HLKHlK4ˏlȦ0ӲIJ˿$¬dtDŽILLItKK L̈́KK(;04MIׄؔ٤MSv<ApU(B=9<8QF}^Xs0Hu~QvTP]hD5TqUu;XTe{|TOT4ΰdR"NS!MR#-V'Rd%V)δR&U(Uk=f-OmV25 y8 4UA5}Pu-Ĝ;yS >=Wwi8px WPGshWEW=xUuԆb[U!R%ՋUG]umXXyU؉ؚs՟e Wdn%N,oVٞVM' ZZfUV2εlRҧ]Ȧ֛ϧW5 su)I58|p۷Ň"[zۻ{MW?W~mGs0XDmrs(܄UQKE]TlٔQ5eG%EEXXusX9( AXsAPNܔɕ=.=V<ϜYYmON0=^]R]Zi}Z|҉OڵLZ^OڬZZA}u"h!ׅݎ_1mPu?v Z^UUQm`~x` U-ʥ]P5U-Uv vJ J\F=Aڵ]]ݤ$Z]%ϝޗ-.-E&.Z/RkUڌt^2]bb]4~}Wځ)8_y'.P[`?F!TUdR~bb[/_k>i^&e^mv'be&VfUoV9< A2gI9u+w=Ǝ>篾|̖l䎥nХa W8Xm`鐖GvUkI~~JvhǑJ^Ziei&^Ȗc)6O٨ )*6nJZq0 sߑ?@>< [fXa$ ='q:B7tNq-1KFLwI3oFt/9 wPAOC7SDGUgum}tvswnQu%uBn`Lc0vbeXcXfxjlvjovcvnovtlvjvtwurwspuwcwxOwqvmxxwjvw{7xwxGwWXe u uP_wܥvyPd7ч0vo+w'zGwW:)?zvxOozo{jzvx@s0yzmu {'aGbƇɗ|ʇǷyϿ|w|ѯ/ͱOw}=/؇Wڷxd|}'}~/~??-H}5̿X^ 8oP|~6n} t Q5ۿe}|||G,h LȰȖ'R1⠊3j#Ȑ"G,I*Wl%̑(+\`Ӧ&9RUf Iڜr2)ԨZ+ؠ?:QqMez.,z;w9qkSAF.l0b?v@ojƻ0npaH))ȳQ2%6rUSUiUӱ` T䬵P{@r^;+9k*M*w|ӖL S³H{ 4Id .<@f0  -ww0u wu# eXVP)2({Bt&qT1LmTsc=qa6mADɍ>Um @s !gHO~dd"9Ed"))LZF>IG6GJPG|%,cKф,s]򲗾%0)a\ed"M0;BņI 9t4aiE¸⩰E7eL DEhF=RNm%k)PPkQ֚yb[#GXJEvr$"I)hi*щFGȒd-EIR##+;Kbβ>)Pc;)Rԥ2E *2?6ЩRVvpZP;mB*.n+v`.@Z?լsk]Vڍw&Wa(l Q<]*6[<$ݱX)c/J {Lc*m\h )f͠e-/`[ohu|PϭӟXm֯>'B&ѼYYHeҕ-i].~/E`TS֦`'Vַmmq} {.趜j:Q9p>ԑ*Vv:=~#T_BNC즈 g(`Wv<NՓQ be0[":6;U/4[f1,layc=X%3Yc~v}rQW.a Cv>o b88~E\:+r+]0W~~^ڝ]걞"GwxEx<|^8 U^ܑM$a =Ȁ舎 WMK\6݄-۵0U`AG@SԀ LӍhy?Zlp D%ڇA ^(vD嘒Kq R ["EO,r͢(РQa}΀uޤEZ1.X!3.PO0ͦQ 0Q," @QpItЌЊ\";jG&&pb)R-T=J=#>#?b"![Y!'N C*R>)/ݺ0Ɯc1&Y3~=#k 5j\tUX˼ŽM`\z9S#O;QACcE#DAFA#S*L=%Q <eAJed$LQND"}iE]3\qQ$\~Hۈ4aO@ NGL@`*:$cVE&zA'aieAT&)ffZg*[b}` CJbUaY&YVDZٙڙťn]AcqJVQy,%ԅtE"Ab6ub!( pbdfKWRnx Q {'p@yZfxCܬWu!W{{I$l-5-QdYn6hRe2݇;?dI=ݓq2I5]rRŏD89$u^'JYvRC]A%XUjZeَ⨬%chh^.Y0 ) ,(%iXk}i &l))eb'=%r[9)(v(ٛ*ߜ1j9@l2*njn*~jO*(J)餚r*h*ΪjvB꣦jv$6&+*2Fj6*n멆"knȲ+ƩE+޴:H*+Z+bVݷfƸ+b]+ha郅,,)l@,5LDlel9b >X%&lr),˶^/fgl,, l,^  .`!Ӿa.-B-J*LΎ)VmmN~-6ˮmlY"(I@:ݶMFOiw-Q)#Q"|6.LaSA.tVR.Nn,-.F.*-۲+e1/]m .ݾ%Qܓ& b@mu+W#C1oqC#VeC:/XRX*/!5TV⊥hfa*fu[&/nqj`r<јNsrD(Cx/HAe$T~o+0^I=/[wFA0Rm頖o/  aGM@r@ʈ HXS_o>OݤhkΔ_1eqW3.)aRj61b]f<1n6J0W R}am]%U ;[ p]%PLy67$htqn2_|{Wzf{ rrxzrvgvqanyb|vrN&*ߧq#k]Q1fIq1i11$8M##:,E6? f^9+ U;(GiMchwE텴:^E\j$֐ud*r)S}o+{rm-g~((nƆ/?>+_GUoL@'|Ovcߪ8xK tsٍU[es4@0 6LFR!F,fԈQcGA9dI'AreK/aƔ9%ˎ*<ܫZX-꜠mX#VkE5PgѦUm[oƕ;n]mx a_lppᾀ 'FbƂ?+1-\СD) fiӧOukׯQނs(O?{m6֬N{Op Ui CTE;zuױg׾@轖7g>sKNx=OЫSֿ?l PȬtmz3NwZKYA AQ+QL/;,F3f,Gwz0 P['(DpA[98đ"и (' ՜MS9dq`GȂӼ>3#ȇl2EYoG!TI)K1TM9O+: h3ipK)J[qUWHcfVavbMcmjViZf6Xlungf%CMQDWy{}eAX&8Á6%aCM+Ls$s׋1^rt6d䓣9d?\e\Ǖy"mߗDYwZh裑ND8|)&`i(FU6laddZزIPFfڵ6y헉n.Vou!Ell×%w@yu?|'Yqo/;F]Gs;O]Va,wgb1|yzx:ycea&Z鯷o~qg>'xZg|a7~w1}oq뗹lN_!@t3 A B+0NvsE;$9 xrgKo~’hxC.ZPFAhBtC|Iwh9$B`8,JqXYbߺH'ΰP}*R f`qBc84}HA4!HE.!II.M] {G 4Bg~!)aJ2f=a Q!2"U2Ty9T`e1SƑK uh>gHFpGtc 䴈=Y:F0w UnVe+Cj(1v5a]u S2q{"U 1Z--W*|]Ra9v$&' `QV,~#_ kE}QTmmINVUgG\ԧY\.Wiu]4x΢3eBblʰkwX,}VV5^-ww_^ֳ[x ՝3r(ڛ8]pwSꌍ5jG-$5Il7 4Iii{,a{5\%CզUGtXvx1uN@шb8C#Rժn k@uZZ׺&6~G\ xl,۪.G\mH+ڏ4i[[m?5m9mnϼn> ڱ },:ρ/}voM񋧭 :\vf]]oGA{-r=!6nrv+-Bsݞ6wwqb詎C;lG/zσhDG\^I'+Aaūn[^'u]ܭuw8eKvK$)(/wn^cC (qy/y~>G  =eMA͟PʑSH]^Λ40 o/pN0 0/*/ޏ/OofdcDiN8Fxt\/$"H ϟz 1o 1Ls,n;@FB kV0BAb V 0 ݐ d ppg!OP; Q!F@aQ aaN$tp4cTlFeql<0 O}ԏ UWʐ E e0` `;1 "2q۱qAy=בN1$R1WQ#Pf1#g p o.vq JA/ama@"P'!! q!r(q)r)R*"*2`+*R)HR,R-*!A!C&r?$42/ˍ#e/[&kr&ˑ0##*!+ű 1߱/"(22۲2r323)/s23 *kA(Os-039s2-o2e7y7Ū2B//l' 'q1?S25S3_-1::c;24iS<a! <ɳ:as;E!:`d ;s6S;W.?B.#2u0r8 84sl}9P ",߳;B2CÓ 8T4=B/t,RCѳDD;EsCU;_4ERA?g4DRG?u@=B@th @DmATVQ W(/p+,2KrKOC rEL*Le?E4;LbNq?MTKrM#tHH4QꑚQ.EB 3>7T=9q)SSo-CuS=K5;ODIT7U_Tc!R=V;UUau= b5]sF[5!|WbP5P _UQQZ?_c< 6!RI5ZuZI=5\\U2V*_vb-6i1V0"c5Ha6l'$->TA) 7(gyT9X"Mvvh6Rmi5o)Vooi5j9j=j ApkD*A)fe),#+*-KR-6sQF$V=F^tOt_ppa7JV("KPaBFU,*BK:ADB&b%+,`rBnKaaƢ@zs Ű>jAJŴ 1}CXG׷?E1}}Qt~}iU~UwWCMu]wHchvokTCf{w{6)wA6@8 a9^EMMVds&WP, 9t*-q!ErFs8x푈҉X1j$ڎ&Ƹpg'$7|@Dh8+Cvgar_EJ'X@)^THssk u2'G(CgGQ)9;1-5-3y=A,??7E/*xjEOΕ[yXvx'-pA: r8X:ar8¡ynM 9xنEvGS6ssM:33Ϸ2ɹ==4#4Yٛ9W]C•CYg98kٖ:H%8@XyysT$c2ar!kEKTq9x Yl9y9|߹٤=s㹞?YY>;Ye]o:cgڞ%ڪ ~D/:LsZ:f`D'F/Wm x#Z*ȢY)F֣\Y!4 3;yFQ|FGc4麯cүsEEzG%@LMNH.ΩpgVITqOt5o۶OwKi[gNk;;O)7)şγ?b*n6 `6v#MsR B 5XнuVٷE5{~D{~;Y' 5$[Ƹd]Zh˛nCGVio/ۭ]Gn?܁C]#5:9}R|2 %aQ:୾_Ym3=ׇ@>\^u4뱾sMYZo}6,Aa#ޮW^ rESEIO߰E%qs@q?uy}$8?.P;'YԸI_T_?%__<[S ܽSu!_;,(HPưa2[P B;(d0NpC zD2#Ɋ' $ 02"ˊ/qѦK@Q,%ҥL M:ҨRZ5k{Ez 6ر[={S} 7ܹtڽKu˂| 8 >8Ō;~ 9r[yU<47o3EIkmdϫO[y־-{m6&;̛;9ԫ[=;bR+F5g}jٓvl);t˖`Tt_ . t=aNH!٦eyx!g%Fi6_VUu[R2~]-_^eF=.࣏^KIcJ.閃> eRN!KyaZxk[z%l_r(\/v2V_eA|Iʥf~٦TJhj%rbiEh""VgbTJy E'sznci&25+ފ+rkZ[,ڞ\ &d*KezզL)z:#Zj0n₻jEb鵱觿KofkSL1 $* L̈( #M!'Ï> O0 ]<hk< glp <70+;r U^Yzs4n~e4#R6HNS V_uZou^ vXWf6aZ4~y'>={ s~wsU;5~EMD[Yinqr*UF M|‹˳$ @{~sϾ/_>o>??)}_h/!(0}~evcp, FVO8#%o!۵ʰ0k[ǷLuK~\"#b>'oL%x+"Q1h$n~l֩#+]*\A/!JH5tI-vb. /A2Zq d d>=I5k'&inN #8MlϜf6wbS"*Nu~\+ z_N %1_̈JW D BjDzIt~(eJG=Jp!iJg*Sn̩L!N=ӛT?P qlBP5t+|D AU ;|V(!c-֞Ur\1RJs]Mu(z+QV u`A2y0E V8U~hIWVa*ɫa-i%4,Y^ڷ–Y)Rv[:ղ]fs;wqEcv̍NjDWɚ.tWDrmRI)qy2m{bYW]|CNEŭ+y+{x.'mKx.,.{䷵n}aL5d0x.)^_b ~m ԭ[Y jIj1"yLmHc5 b,-m}Z0yd.ό4yln g6w^dD_o1&0yc_ m1=e15cE@VhE[:d1h1Qf{řL|g@V8)P_ Xzִo\z׼1gЦzJi&因79ԙnqҖ窽kcnښW'jh֎9soRqu m0z^;URr l'Π>Hhއ2`xĭpd޹:v"H3*GnR*_`O{F䛜9a.nOO$6yeE.я"N`#([?V`@ĥR )8 lo{qq}mgޔk% ,ho]3!?ϝ}^J<7'snJ>~s>TYyj! W!z_K7S T{80a(R *$/ )@R 'Pa F]UpE, Z"﬿NjA$@hX'dk j{1kFv8{8{w p  gG|I|' 0RT U `}M1RR V7TwUQGK؄MQHnQ tr0 \Jv 4B ikȆmoq(sHsH+$$$@WvXF tKTGu Au `g7 `vMS< dpWvS(SpvpDhhlc u t@a(I (` w8t!hXȍߨֈN č. .Mm'.),..+-^}4ʽ=;.ME^GKM QSN(W)pa77JbN_Zik>b~ocNenBnjs~Tm.Ytm2pzے遾镾]ݎ⌮K.>s_^ܭN'.*N^z^뿎n~^ľE.3^ξ짮.֎$08q^e.i=NͮNnl^h!^ ?_=NR-N_ +'(?&4>_8o=,.N^~P$~:BW>_bOdOhj)^.KM~f薮!d\v/;~{N1WU-BNJo}_/?=]o&O͟_0O>OOq_65eO/Ho(Oە[[O)X0 "hC%NXႂ 36DQE!ELÎ 2HK&?n\x%L9)Ɂ[$ƞ@,%ϟIgyՂEl"V\-B T(V$ɑ۴q+=Py5ku^%~K5ὃ6̥pJ\U㢐ﲼ͘j\YʢajںXձAB]!m۹潻wnܿUxqǑe9展G;z\f~{?_=^ʗ+0LP' 'Bp 0 APDI\Qvu ygAav_ցr ,AF[! [h0ZjH.#!6@ ) ʐF шGL"ģ)M\"&""LH)nQ^b/UDcvF5^cd#爁ڱw\c(=iB&R"ߨC2ld$IF MV2,%9?VT%*)CV4(]IXbҍ<%+yiK:fe0;)[e,LZR&4Y^&£avsZ.['ٸw®'I2A sjLA#'P!s CAhC$C:P{( Z)IK꽓4"R4i@eZӘt+Kӽx0݊<yI)К*UtL*Uj՛ԩ\UU5biSJR}ղuiֻԭ/N .ί^˧ճJiX@mW5#x [$(JMKG8c=1 >pF: tXhnFo7|$-l ͳ'4 ZҚF+mk;HU ¹ fz`]Zw+ c@~C4&﫽04 J(, zi,. XP4r 35>4P4zTWGJ (:R pd@`1hX'ua3E{pFY8 tVC^ZA,b!YJe}hl K#KJ4S. h^0bci~FFC"h.FgrϠs{,:Khw.,NUs֬P{\%x+dZV60Kj<`'tH`["P }ݢh6[TE{ _o:G}\SjrWΜьq٤3}ome6=̓81 b< k9Txxqkn\e9\_W*Arh( -E3h::d|4D}ǗvMr[67qi;\"Օq[1WysΒwU"9Xplb]o! A>%K-n6>7hmmgE+xv[Ym26x oJ7g {.i HuNiw5ݡVŐ7{]hWvI?mz/m4Xmo\}kC|廻/.pb1$o4a8.Kd5z.*[r^X/ @ܻ;䂴j( ,+2&ܽ,T(;/D0 *%555d%Dd-T18.d2t<9T>$:,A=C+rJĭDrDzC롃U2KJPQ|R6T*37;L̽YtZBBCY{X@`$[$'WDU^a4SlTh\idW;ۺ;+;9cnS GKƬKGp<9wEG~+Gxdǯú:{vfR}Go,Hs~\G} {4ȇ3GSGH@H9RL5RISJ}ԬSkeҢ:mSkt;]+-L.U:$GFA qr6cNKUD K ֫ 7 5- >/{ʧ ' Լ>31,d# Wy*+> ?2ܚ< .Cшu4s=3Qt.,UJ B$M3YVT b "J;T@@M-$A Tˑ|”FWu\Bp`վ\Q]ɦƧ=dZJ¤ [D[T[ۼ Ph]٨ںMɯPZt[[Ȝ[ZeIxZd[$e\WIJ7}s\۔%=ÖlN  \\ge4 ^]Pt]T]]EFmʶU\\ES9-^]m]m^JU-]M^EH^U?U_DH5T}_>%TFu'ҍܴLN[%[:EU^`]{5f`va.a:L &6RMaFa^a'ޝa!%}5~=&`-\!vVb,b,^-b/b0c1c2./b3Nc5^c6Vc4nc8c967c;c;c>c@c=dBA.dDCNdF6!EndINHdKJdMLdONdM]^eIeTdU^FneW,UXZbXBe^d_eCfQfbc4]]+^R-^\؅/Nprbbm>fPbvKPkcj..fmbufp6fp+J_0_[؅v0Gb}e}HpKgc6h^chJb^gP)b,]SNoް+brpgb~]i0FiN+ΠwWhib_fhj+^Gvef1k&d.>]] g,hbcj1~ik.4,b(g.U(}pTюb.,>mFvyp.mb_8&lqVlgibmHxVNc,/f,fqmWjlX%Z(kPo#Bo."&o~ooipoop~f'6pppjwp^p Wp_o p7  /qGqWoqfjvrⷎ6lVn,i?*r$kr$w%_n&(rp4p.Vsrqq:r9.rdogs'mO_sjo\vnԦҶn,/g-&i4&u6Nnn5fku,NqX~VhyPIuEFvyVv9vvFc]Xjg~j_Q?n&jYS,oum'ln7rufhvj^.vTo.T'j(Zև[Nx86xFxN$j]vH0iwg\81x/vyt'xz\p.&yxӂkgc蝷螟_u1.q/GW/7?'GOggUnz{a_{^X|W|M(\$n*va'a_ܿ|Ȩ|(ȥ % [4̎Xч~'ۇcLx' ƇZڗ}- ˗|(6}jMUW(b(IM_=^_UE^K^__} 0(P  $zBGt3W#y-y=]M]]9]!əM&b&Fff~\j/'B斴u+I*t8ќ[ z[ܱ֡e/ /$&=ci /j649lCxbVW0V3ƒHkknʫ*A/'  =d.ĉ J Cm:JePV/spAO3vY3Lɻ|ͳ0+C.*t}D PKJ:^rV@Ot@.@t! ($~Dc{C#J1QVM$F4QMZ<Ȧ6u#m;ƣ*0zvTHLsdUAU]ܤ"HqKc$`I ht(ʹRN+BLxԤ"-ˏcf0IQΘ '8ݲ@1d8wy;e.mz !&ٺNy.&9ٵw\g=iN|.v,6Ns\'j 8)JЮ%T4(U,|j@;)ASK'=I'OJ1Ls_I ҁU8z}tj#) Ag*JGg63Q12Օ>mSI?"b+Y*լrCUkS "5c5ݚ6ZkZ*Wuh]9X@pk\VJ;2v^ZpjldBɚ6m]jYa6a$q<}z.6` X귺E.D8BO 8솥 cnɄK\S2rIܜTpu]N!n;ވP7u$Gqע׹ZBnJUBv "`.[8-=n< BtZe KV>kUi by' tqm ӎҽje/2/ndX;@#ckLC.R$oM6ȓUXKE-(N*qAjldeOb fr`$[S.r5f=v>3<ŊXG?z` nRYckc GOhIAV4 EvEhFK;u 2S/_Fꕝ׮4v'SzȤ.kfKб>g : Wjj*To{ L \1?]: Ī;뺻 q|`Ὥ }u^.( < yo1<*|xĉm刔eg]ƽe{\e,+c6d "8T'u9? C W;nua&DZPwtG76"w˥tM z|_yFAi#)+ߝ+zÆU` #l[>>K(WO_Yt{׾}v'tJ;~wucM^!XMAPSϧm@= Lp8h ]IqlOn x9ʍOR b J6   ޠɍ`ˤƜ|⛴9a }.A옣0we8\ k\UACs, WoYz yOtpa  J"#<" DP"%& b!o$)R( b(+&b.Z ypKb% mnacmVjV@#j=Lce5r6rpڭU:^6kQ:c,cY5'%g]3֣<ޣ$`Xh}j$J$T)d7~DX !ϫO8 C.dK#D^OGc)r$K&%Ad]d%EPUe]l%\I>U^E]^ⵥi\]ZFe]G^% YeE^a%c&faۥ^``Ced.UDeeWˆiWR:eV.HhUJ5$jmnnMQ!R6`&rc#U$5.9&'sNtFf<gq2%wfuJIv&$xZx~uFA¤vv'z*wΧ|RzB{'~yB'}'Zgg=NV&aigD%mRj>g{:S*o=YDXW&5QRQJ搼h-(21ϧYڜI[T^XR  ܜЍЌ$sdVRB!)g8Qae:d*_)je)Z*-EAfg>&*aVfeDڂ6fFYqXb&ʛIrh|jzjzѓ ߶]q%l }(nJZ9 Rj!QmBmX(Rn'y-B^"Q#~k9hX'Jkhg'F+N²g>*bȊxMl볂f,¶lC(iҖRmk.GJ^ެlDNV-~*<0,̾Ďm"ms IzL1-2"h$L|-JG!jV*`v**.-bn$>-.]6.>FfJr&)Rn.~&6I-r$fѢLTׂz%*hfV ؖ؆-^ٖmnlv.^lb/j>r,ޮoo*mBsJ/f/Zɦ2/m/BD3p38pKSp[cpO0kp{spp  p 0 pp p pG%pK|?v6K3;8vx4Sx,7x3pW08~@cx0x{uH#{wG 䱄ϸe#X?2<29uB+D9y9-K98.P7y%4yo79﹣oug1g5t3W6/CzGzK5wtx{mx'K7w ;󺯋';[iKw697Ep[:7;{_gu_k:qoKysSzk{{;w;_:{.P7{`;|k7Ab9;0OK8R'zbrc37XvtigO:/üspw330_yg{ΗӶχy "37[͏@Yﶸ:'9Y/vC9<O~[>SK?Zkȇ}st&}݃3Q*zWŏp= r.8謼Mq|=2(sՓ8?x-[D4("ݶm p܈)Vx#'pubE.'D쪧KpaÇ"fyō?Ydʒ,maAt₂~E[o^˥Emy558L-rdɓo޵kƛ9\Σ~&}iQ*k׳+g Mܻ;*pO6p}/9osONuٵޝwy7|og{ӥ_7~?<$C+@SS@sp!dnB$0@!=pEM<U$1]|_lQmqEqܑu s"M$DR&yd(WPK̒L L ,3R5l(SH*YOs? T% ECLT' GoT)5OaJ4KJAqT)E-USQEQUtUVaN8pSe]yW!} v_YuYOk=V r6E UEuR[PtB5.i9 eyWe0hxu^y=7{շC~ߕW`{w`-` a%~È)-ָc ֕Cx96b'~GWNaVYgq9w9kh{7hFݧ.1&Yiv臣cZl9鰭VkfF[v{m&v>S˕G#otrE+?B3t2,]N;st@wtGSDZt7[_uDW蠣L!FD>xeT7dzRr{O{ç~TUYt}Ŕ~u~O h?4?)D&Y0Vc&(Az0 4˷})D Ct>Ni@t`\ qD6ѡ +@b@( 7DpP$<G#^hC$Hx?Ă*J,P:ARn\L82ltDIC1z#=I@Q8GDp! )D*#(C[ QxK_ $u4RK'9t=C{#=dJT#c81WO,ȷ-mଛ߶6}I7̛:F|g<ɳ}3D: PSjs]h=;KfFOMj2f/ICΔRV9Sh::ӼŴlCe#!.\H?ċO~""!S!).@ J,RFIϒS?]CQ8U{%{'wtxdZuVZI5k Ւ .@S$WbVg!uaZՙC0dfL#-ZI;ֲ{n;~ Z*-mg{f{v[:k힮. jͮmz)N] %Pw jSѧ/ܲV#%P`uMKo@1萏bd0 #/1 {FT: (EjNn9A!6d!S952{ 4J(mP"'9p;|*㴠6kL)CyeV34@2Oc*N[wՊ%Ub+vqg:XkwWbB:>]h:viѨf4=ԍ65A]Vzu_Xz0u}Z3<6y5=іv0mk_moc]q/Vvv]o{ߛMo~w >x.gs{ '^qsx-wxIގ'GU?_1V"1_9upoyy>tEG:-bԯ=qH,fio`җpC-؅0`tDbv֡q($̧U9/{>x{zk@}Q/K~o.mx. `̽o4=ͽKFE xh>_~}cq﷭ a`y{8v.F>.|wOn[[/b ڡO 2@80 7ޜ -dno6ުMBۤ(Ppa]-x' 0 Pڤ p 0ڴ  0 0 p P ͐ M ˰  p 0P` PJ0 #0/Q?q=q -Q;?Q Q %CA[0WS1GiQ_qmqېďx%rAt0pPXP]P Qq1Q N QAP?/om730%P2!!-1 P0BۡR/o}N#:>HڬqP _|"=A"'r.(2)eo)-;rho$&-R$S* )o/p/ uA&ߏ*RK$VAڎ0 )rކ݊4(!R/m0骢%M- ..OS0l,MN2܏%l 0Yr0756E6.7qs7M3cZ75p7" g~s98s9/:o:/899S39:'S<33г3@9>/? o???.@S3J-F` p ٮ%N( T*4jDA A]eB?+BeDaKCC-8ngDyk:C@@DKXtXxTblLLTlF @I4G|TYTTHgF:  DI-LtLMX@:`@QDAFEJJg'FΔDTˢ,̡̠͞Lk ^.@N%l`"挝uQ֌x RPP@TE5U;Z5TguTS7V3TUV U;US]C4 iEA%`NpBtutHZo Zh*XJSDL5Ө5]ѕWwvM}M_U\T]7N+%µhm_Ŕ]\O,Y'`A LVv^#bem`=bM_%caKVYM^7:Ć-CYvC)VOC[h\8rbce6[aQDzfWBaIqVwhE,(C,D&KD>A8hcwQ6XC>kk h%CjDkn(š!XzHhVBjr- (h!ll{nAVNH@ 7oh8mZs}6]qUWD(r=u3WhvH~*,WXykwlsttA):D7`*zSʝ4febw:Mb !tdDF,*ZW8wsʗ\RZt;$GIת:$|.%5wExMb,Oi~V$تn U_bUWvV}WeUqtX 5X }UxUs@iwyeC IDxXX 1X8WV?5w8m3ijV,vhLw$ FSH`FD{VDYJ+|CwGtrJy;W=Ʒ7JF@2y&}TEz6q9ĭjxmQIs X zxvfCm_CcDjYn9Wj9d|LܵC:`~w(T7j%kmw}WVb vzlY"m*mr=Dȝz dh! x]B g6-ԓ6ڣ9htdS:eQVcYc;zvF6a3UvbbIoMRPzU]zeaZEɧZzGٞmluZt#lE(jLg6AZC?OtZ[Ei:zzZdΪtIڪ9Gz#Ͱ;${8سmf?;A[EWIXF۴U{[;_:W;kʵK۷YgXˈ8:5X}Pvck5dں۱q뺽e;O@hf{|:Kڿ qM<;|RB#cB:7<3|+'|05"dd:n#)6dc'| F@Va3~,@??\??ma b0- "$(!&b#D s"rġ&}\&xo X@}80^` E@``?b^aq^^ߞ^~q]*>^~ _ ~+%#19%;C7?5?_?Eackosq_e_Wm_}"|(X飾o[Q^{~ bbޥ i}_*"| T%q, gP X&e+&ZѠF?V IdƒE\ɲ`RĂ8sɳϟ@u JѣE"]ʴiPNJ uիOjS鮎wkkWrKp\mJ:lA_ŒkZ݊@X09TEut1N 71Ҕ-3YP5ZҧS#O֮c-vA` u_,Ct.ٴ&7yTk"Fk`# Sn̫V+8B_|δٷί?5hXr5.c\AԣKe ]Dlaia6) Y<YST=#N z@Zw{ bdV-\OdMMIR2tWVٓ\vZYJGijfզoVlٕJyUzyfzNg Jm蘋2e衏~ haʨFMxjsWVj *무j뭸+kk챻.lFVkfK-v,ކ zn޺nnJ0oދ/n pҒY6o.{N/μ³ ?_wѮ~+ ?ɯ<_H%p|rpT@VEmjp3Y 8g/Y u6Ûհf7 cfo[rX2"̈Cbǔ1&^̉bFbjC1:+!ы"02ˌB#X/6k#/:K£3>ʎdG?ѐBd"Y+A⌑$$9+Gf,).NL]e(EDRz[,ؙZrHSL^YXR$&J\Z˗Wse/oIaJr40iKe6vhUTqń&5mR IMW(hf|;ʥi0`8ϰyS3O>U Zߔg2r/˫+vwխuxPC_nxmZ9U2~`WzfK@"] WƝ0*sA\{ѽpI;P uRuoS pVc9lMa:$E|d 75?he)UZn2,O?%Հjr\ ɸUq&#bB3/Y`WYx9y^qKh^Ӿe٤h9n:_5jl֣ݰ;7ϡzIz*p]kԯ%kqhe#{)n-i{-xq^/]7vͮ><\Lh^'* #fpl#8QDw{+&*ʋ[i 'ުƬM+\8&"rL bv!6P E&oXE^U.ఊ,h#BoO[6y6 V=]t#^Ozs|VD]et |]xa^hV ôxAqYU@t 9y=l[O]U/NW~M?aVWg9調%| nyo~2 Ĩfi'z= o{,\do ϟ86D1Wr=_^_>99;gn3LHu˼o'gjVg̓qfbSTf *TvWQ6|G|} 7eV+*H"UoC!fv<uNE0+#⃲rx*}Gl7G,T+'zbyF{Ny?,DhyU{Gw624fss3sj 'o * qW*IDB}C'֧xX¤l8+q(sXH0rY &EPRoЈ oc *,qɧ!ȇMhl(C7wXzD8+巊(!/wzg@XnWҦvcmmj|vxnBhCC2xjj‡mk}N7ƍĨHhǏHF4Wum∏&&VwR#X"p+q- u/)uy&hcx0ۨǐcW6YR n>I K)2ByiS%(W_DW9)hkՀUIf8(_9tVdV#(f^~p9U%g`bٕXmoqVyfiyn9=OyᘏڶGx\woş{Kʦlȡʙ|_|  ȶ\;;`zEM:b&Jk!M2  HPB ,x!A#>T(aAC /Z!GE2 1eƅ+M.mdI$N #oDN5EKO*-9K::4*ODk-e͞E{aBFn\W\uśW.\}ܗo`… |XÉ?8duWrfΊ7w,Zf%ă1Эsl{k߾;mݻi xÉS]4:%cμXW|{e'w|x{>;}5p|{^?-0kϽ;2\ApB,*C=/AM<0\|<#1tTjLnT.G{R(/%ɻ2,r.wc4'̍LM,M-LX! TO |PN-QF q$9C}tNq7GYRC U"}\XOuQU Wjյ^1cլQka5dA[6@^Y6Zk6[kO`*F*d * ]vϥ몤ڍ^x{k]wߗMW`}5WށU7wJbnދ+# c'8'S^Ode>doZmwޙc}Evi.Y vo(!yVG k5ZoUR<6lmݾ:lb.XfmQ{n \oM\-q!͜]\I?A3 }}=SQ ]O_ʧ\x& ~Q<^ޑ-2](}-E_ wXGJo7ם_c1ŠC>> PMw(=-\q1W%iXv0li"SW}@;aS8’ph* QBc>TYWh1f3TB{" X'ƐeR! 8)&qH!k:ԘF6ond`e) kc7A bh`_Vd9T#Go~cŷ1̣#GJR|%CINn|J3rG[)K.,gK2J/bDsJ„13)I̤ 1^2 zKj^~VqDbr<,\؂ B4MzE\m!F@t`o Bʐ~c 1b^T8],@vq@2tX$bL 3}5"Ѐ.)#.͸` B~㩇B%,Z82 tteYZNt&.~*zSumSs$@l-(K `rŵU.V.X*ڑ|u-k-mfј2/x] fͮ! ʧ@Ԕ2uƅ]iYfX m Z 6#.CD{R:Է qb]µ#VRB V_yXCf cD%׉F< \Q_Qp j==&7*.C\Ju-N$+uIkZdHD׿DĢm|>b˳Ev(!_',dtQ.E)q-W7{5ޡͭ :Tс,Xyɳ7ȂJ.tx_ Cr 8P4-xC?< 7b9'=x͋Ȋ{ϏNX{䁖ݛz"gɞha?wG;kyy~ oU'}'>̳s}x=Ϣ1zuwyߵuϻ"?G ûӷ:+Q88:s3T@c ;> k[#A~A5yaX:- ]؀*[ȅf+~2 [C?A@[z4'{'}b4mB(6j;BC6C3)9:;<=>?@A$B4CDDTEdFtGHIJKLMIOPQR0SDTTUdVtWXS$Z[\]^_`a$b4cDdTed-ghifklmnopq4Fj4sDh$udvtwxyzVE SE<8T?EEUU51=QqTxqQ<^݂U5 f]UhTmQ}W;Eв)mzeҸoUVCՋW{-X %SULmYSHU-}VuڗY$-,uu$e6E-ZlxVT@T

UUMT\xQ=ZsR$MRfU-( VYE^mRT.mX,mѲm_Y]G-mO*[:X!nq=U}bQ:X\ W([r_W~pP lI|NїEO8`h%OE=Zf}c`\ W. .]5WU Zs@ uSU#6X]ۧub7ed[EeP4V]M$QnQ= P]Tme4^c4NXHVg-_\9N\YX\2}ARi=vXmV>ZZSfmmbNLb&P0Y(tfmtPZU= ՁT^`%Ne$ 5wreV\]mV}֋]=aXQfQe Mk.EivߞlE>Kd-^B vXDiО~ԁ5PQ-Ќp EkiQ$iPeӦװ]5jU l~NkUsP>g.ETly]qm]VWy25hlm!h>mLmGG,-P!,#g&22@@^(nZ/VHr 23NKKf# 5M@h6$"*#@BCMYIu(0$%cQH*\𠀇#JذŋN(1Ǐ rɋ#I\`J-c|QMt o HsУ(FDʔ 6)4j%լ[vUeXcɦ ۷pួ]$˷o۹ūr7<8aĐ(^<.#C̐`jFy'B  Zz l8X GΝq'.o7Ё@ -:\ @BvowAxL=3zC7 T@A{IPX[q A%`1 0Q7!%!6ȢP.hShd2ʴBhc!,322KKK(nZKKmmnjK mmKK KǪmK;`x(}8mE_Y_KNʳD >!pc;>c}&Hq6|i?b%߄VQGvawGqp ZY`5@(0RX`ވ#@08Z|b<ɨ-  P^p IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2fFEЌ4RӴ&6m^̦ MrΙN@|8)MxӞNg8M~r@ ā4=2;|(DsH&H@P DE*Pt5DI!!,,KKK22KKmKKnjKKKKm:mKKmmKۏ:mf::nj;%%YueXq\Vy픞@ k( Q`A } &@=!I[s6QhVX .%rA<5[+:8PZ!B<!BqPG8|@pea8R $d@u\9cM$P1%O1=0褅“ d)d dH1tDꪬ*!  0C+! 51@ ] F*A*vk A 6V rm˪ #hA :X螪nP¬t ?@.ڀ Jp벚0$ /ApQ- Ѐ0o%lo0&Z@߀%|#MtjK[|sש|qU\-h{MnFʧlZk|.R qm8egS0-Mᘫ\gH]3䀏y W(A Cg.n/J'G/=įNo|wއ/o*췏a/o!,;22KKKf:ff:ۏKKm:ۏ:::fKKKKKmKKmKۏnjK۶/۶fmm;$iPA=ĀR@ 9Mc6̉&N!Z80O *!՜4" P\KD |I?! $HgQ0X]v B#%4~|kS! X&$ꁈ?ilphL=x0L @lmaxժV|-0`{)&aDXve (c_h)ħP}y7OԵGA $y@ܵՐyFm^%(w}Q0a\XhVua6 PL6T 6DK>LܰA*Z J(|yc2n_9ihl $tixr&矀*v&袌^@F*餅BJ饘bjhv駠*ꨤjꩨꪬ*무j뭸檫+k&6F+Vkfʀ /ݖˬ+ĖITP, 믱ꮫnN#pb/[  Kpv< 06 k B-s0a3dM̮#'4*{ {10!PB[tA< 0Tu\!0Fk#@l,/=3#r@#=md 6G,r|=A"GTK ɣ~WmA+=l*9@.;B.DK,NU*RZ+PX/LT6IP=GI?D?DB9H@:O<=WTqBoCA@=:z6q4l3j1i-o+n+h|+\z*Yw)Vt'Qm"MlKgKaHa#E_&@Z,7U.4R02R11R11R11R11Q01Q01Q(2Q"3R4P5M 0G$-B%,A(+@));))9((8'(4&(1#'1#4!7 8 9 < > < 9 8 8!8!9!9!9":%)EUFnYHqYHqYHqZIrZJs]OvfSulRppQhmQ_jQ[gWSccDdm6hr3nx3r{1v|--2=CUthppxx}xxxxxxx{Ȯãſ˲Ω̩A4333f3222222222222211111111111111111$$ H*\ȰÇ#JHŋ3jȱǏ C9їIWPLr˖0_ʌIs͚8o ɟ@ JѣH*]ʴӧ OJիXjʵׯVQBKٳhӪ]˶ې`ʝKܷx˷߿WjÈ̸ǐ#K71X^2_faʠCMЄAV틵IPY1g65U1s۳﯋O N/Vu5ܷ}UfRv喙}aO԰:ouzxƾs6횩cmnYyh&XU\zVUs~H|if UU'&o h((z BaUg~AYv]/81 ~p7]hH&$Y˽`{᧙5w5LGȠhgC"hFvMH!]M JeD栄jh$}V)[f>e5!Pu覜virnf՞ ҧu骬j_~Ejz׫NjUk\j&kT;5˓++!@־$vd k覫 $+kJۮ+kI.ϛW`@tq k$\\ :@RD=4[|8ڙM;A_4.7A 튇N'D:̔^Iߎv^Į/RGo/|FC_k4x?}JW]vGU$'=nhWu% ĐQA 3+8̡lK@ "|HD HLV$:bC*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRz!8P}:JժZXͪVUN` X֯hMkWֶͪl}\VX+^jWn+`+W`5,bU2Tu,d';Rb3r,h+ѺUU-iՊȮj_{Bum֩-X[Y5VZ+tkRwֽ[ݶri.xkzfՆz;ږѵzާw/j7moU _o[^#ow_.ةo|g8 v./\a  mo᨞ŭ0uBx/vi,EK[ ?2mظ5[MGKP氲+-83mV8Ǿ] "͝kE:/c`ܿqɽpݦmqV|ַ7}m^WiV۾Gzy-|{ 7G>fD*E}r v溮6uomvwkޔ[,2OzWNvcFgzGnqwzf%yу~cVuLE^wQ2+7'-< *Ќw߫^狦3^i<9obϝ 7-q X;l%2y;=~՚Od-|oy Wӧ~v߯kz͏jO:j_gvWtw  ؀]h~ffwgȁ!Hyx ׂ0hs2؂*X9>h C؃FXHL؄NPR8TXV7W\؅^HYb8dTej؅anP؆p8n(txdhxlxNŇ؇ɇXUM؈8{Xxsh5GBX/ȃIxJx(\[[H[YL#Ћ8XxȘʸ(8xȌ՘؍ܸ⨍XxxX鸎؍8x899X )Yx9ّ #"Yx(*ؒ.1ِ390Yx8;:ٓ= 2 (@NM)IhRɕO3ITЋ  EIZk mo`ٓsuwYyY ÈSظ1I.i+(%ɘ# )` ~iY)ɐ 8 i)6` P` 60yIٛ (ɛIù̩ yYYxy9 iSiy)ɝ홝穞Ù ߩɟ I9 Z :y j9i* ޙʡ,j i* *'zj+9$" B 3#zEʣL #ЗؚjIUjҸ!Yʥ/2) c90#  IʨI&)9qhɧHI L9  9}ک4jʨ: zꪱʩZڨzjjŚj zjʬʧzȘ H` b`Y7 nJZzz2  Ћ7 Lx ]k_   J07 K d@l11Ћ5dX8P;۰H*P I`2 >˳1\f0)9 Jj LЋ8 겦Gk {а{85ЋkX 7ЋJ0d[ P@;w([l## ;I . }j2жoqۋsrKE@ЋpxzːPڋf\ۮ^{J Ĉ 1;Phf0v{|J>Ћб[닓۲#[Ĉ迈; )K[ xJlIIøp#za \8[ 1.<拾khþV"+ۋ? ,hۿ{#`2 `|\R IIJ>ac<٢IMiMj4`\dLl\YC^#> ;۾äwʁ˹B싒 LT {KSnFzPW].DU\ޣ^XNCi濌DNl.msZo)yJMThj# ki{*-@lK1nN^^ᚎΓ>׭n~~>ތNޕŎ~Nn՞^Nx}z)3 zzֽH ٌmK;#X]`֩mޮ$MzվHַ}'&˲ > [ԡ[a^79({ջrMx##_܈ÉK8mz.\ . | Q@ KYȔDSfuװc˂8bϚlB_)4A>lblZbǵBPF)# `T8T HUejDFmv6 ^.xג7^zmѷOTU+g.Q2ŎXְ0D(4&"#@p# M8F Y !(qZPibK+SV- M"3f´1<{G mrNJ4r1$L)ZgBQEdnB; ,ӵds D,%T،&ToN>O/PB2غ3OBـUtJt;J3ekSNPtLULUV] TVeUVTm5QuO}LJUC%`aZZ>[:e$! Յe^2,(nBi-7/y߀т >ע"`AnQ4wa54.< Ev䯌 M>eP]f e>fl eD)P^YmGhhY$Q' %qik*{eaZ&V{F{>m&[o?nWo_q-׼0#|tJr!7=uW?C\v}Goe=&Ywtۓ}ۃ=*HUw{{qzq_G}'9߂y}_'@Ѐ_'j0k?NeMT4 _ R$" Ѕ-T e8CІ7auHЇ?bC!шG!D&.QMb(E*VцObE-v\bF1чd4coF5Qltc%G9*XF:ь}H*z#- ( P#IIV%19Fb#Z*BA x Ʊ  \8C 0rsd*0VP+aBa򖚼;U 5D)@9}JV , (M\&S4љN!@t;BTvpwgRO|c×X1L$+Eva)~!N2yJ¦,XXJֱ~f,+h:puaf_v6(iYX >\m in-h2unaJB8{#ڨ&*k-ħ$lfL^%Q ckE"mK>YX_Д$@mIPy}! Etִ.)wқNgq%{ScZ]lç&Yi(x#lxҌHgXJ"/l.r Fxkr.9yC<>ṄsQ|>!A ]F?:ҋt2N#+ T1ֳuoֺ#nf.u?q ]h(ݧPv Aw@ ~`+/IxBkA " ,~A^/4=E]C=`l27_a~=eg,b1|1{WIyۏ=xߦEUs.><_ᇿ?a?> I l?;ɏ@DS@\I Ɏ@LɞIɗ I Aǀ3ɛI@t LH*(J\ɡtʰ,ˣ||q78KcHsʂ˂wthox0\i]pHAA{GD^,C݀)9XZ$L4DM)\ME֤Dl|K DGtM<|PQ_N}N>ab cAKK\OE@΁EFO=*`N$H|`P֛GGuLs)PƔPeP:=Q`˦0OSʥ\MTQb=,PE Q1Xh IAD; QIR"%[R&H#I$-ɝLJ* 'ѳLR ʟ2ұ|LҖ4mJ(eS+@'%9RQ NdN^'F (`.v!b]z)VӜ8vS%`!b3Z49FJc" DS uBK,dGNZ,b)d3d!c^bIːXkS8>ݑ^BVF.e9fd:~6RSe cSl(MW3EӾA3  ߂hF-]foe rUP-fgpgtvcz{|'3_sn޿TvYWrlȃd#WZZMߊ4wݯ i6iPiviuZd'DNiXi}xiYtj 8N>I=nh(>۶=}jꩾiU[٥ {N]1lxݼy|qXd>lIY\oQYnXǶ ڌl̘.!Y ˾.Ͷ VUMmŶ.ٴFleml ݆m&.ymцf&O WqmVn WVmM`Ug-Ub^q%o9DTouooNp_kTCpYpTpp UEpo]qpG iq/VޣރqX=YErrr,f2e|c[Yf)娼rrZrTr=%* 4e)/SjJ-s8W+<8 s+Gs,=M IoqeMf]tNAQIJttde O#$$"qQ7_r%GPu?uUouSoXpnRUXYu]u^_gqY]/vawqc7W_TgugovWvdGCokGplWp.u eKtnQ%vmxؗك 3Mvtf`vj=[@]ά6LMbOvvvmfh_(+[~w<.RbU [d=hhȊ4mwr(#%f*R>S-sM3/z0s4so3>?o_z5z@GSR󭷐JX S/szdxBwSmx5kΨg'EFiGWyiAj4pp+y_Oy҇|w|&mn-)nkgs_oooo`v~b}K}>~Wok'|xO~~ė~WtgtMF{a!r~MGtg[|AƗg,h „ 2l8BÈ'RHŌ7J#HC,$ʔ(QG͜!q쩑Ϡ -ʐѤ*m*SPk`nd>pbǒ-kvםaϲeVڶrѾq.^pu:oͿoj%LGذ3[6C<1d)g\Y@Ώ%hϝKFmzq޹?N={kY׶̛wro:wo݌f;!6/?~ۘ>kտo<]|_ H~*(!h6H}E8!]x,!V`Tj(+O-˜3VTC+T֎)䐄G*1٤AOB mV2Spu?]YS cGYC;6)bjџ0+&&6Zأ3F ؤ}Uץue֦iuէ^j頄zW:ѪNԬ>Ir+⪎+ ;,ʫ*,",JkZ{b-v-j.ߎ[.מ.ۮ*P.kֻooJ/ Lp,o 7pK| :*bb  :H)〳 gG`X+ L(l 5[lE;*e Ek6ʴPKݯ$Y)jѿvtOG=fи.\$`M[ڹf}N>6Շێ6Tܹ2п2̍+]L.qǷVqU A,$r)}Knk%_!e+*5> +k/x?*,'=~z]`+*Jq\{Ep 5߾;Kx3 7‡N­ WU rU^AÕQWG<&>~0QJ(?\N"K?Iqjx+ WnUg5Jc|#q (TN@BZd#H;r_d2Y%nqlz cwmP_­AҍWU, ԕ_[#ZRWY1!S0MIWd&)dNRy_:Ҙ 5{ȭu3%é+^RnR!>8Mze3{ApVաT DIP`d':e7= "%ruRȔtiHYjҘ4%c= 8haKSB)J:TϐCXƱSPԚ5ZELjSd1L̨:y'A@sX֯+YיN`?}+]튫zL#%VDm'ךu5,e[BBR5XNފ-e٣z̴%eW[-X_{X][Nʵjv">8z3t[hBRמ{3]3ws.'h$W녤$;r7贯x~RҊ=JHrU>> Ԡ v%]x;lX [¹ZRBvJݭR1c eL[} 2ÀVa0A1W#'ڃ&'c6XrldI 'ZW\ak`\gi|§7W:qUCμ JBݳZYx' *{4Hsib.ec$H Ryt `~zgZ,5[C՞]AV>$mk\_@vєikЖ6|kkj̠wܻ6"n`f/wP{֑,/ٛY/k\Xgp 0έkdzr\Z/9Pl-os<5/c=WQ}E?/]M)uOUdCjE:^+;A1{n*/=qNۄDb{BD1#w$}I᝔,>J'Tx DĒMEK_7Oѫ=y{iCz9}"Ǿ=f/^$Cڏo!WφߡiY }[WίWʧC }{__"__F_2 -R`G]^ ~ NA ` V\Bt `ڽ! `:!JJa`Zjv!ޅؕ~ڡaٱ!¡عaQaaab@E* ) ȂVq$$a%!1@@*0 * D D Ә,"AĂ %ޢ%b D*܀@B"0:A B cA)@AL4  (b6"7*^8! |@ )$00@0+\<#AX /a9*bAb\|Y@: D8)@H$12#Ac=@tA#AX06J JiJf$V$N$OMҤJ$¤%dMRK2DR&%T:ew܅BTnLV@C@DN@#8@h0G5=nP~Qz%^n`P(B@F.6A"!82/Y(D<\=\:H@,@t@^a8&f,f/D:Ed#TT44f?&^ Hv7栨9b ĸ:6* cAc*b+"i!rdx"B"F.'wf.w&x~*'A'bgqggwfzz'{{'}gzb瀮}'~Ɂb惆Jhɀ(h, f"(n(V}r@B(D(pň~ 6(hlĨ(g@Vď(g*6,(_u_$Rz%PZeQF%K$d阪Rڥ^em)֥Kee% R.jr B:)Jix&,4f*~jrV,`7zR)讒gh2Ik k벪IhɳJ:kR"딮궊g귆&++++뺢֫+ګ:+l,+,&ž6>NF,^Z,nj,~z,Ȏʮ,,Ŋl¬&Ь,,ϒ,BzB7 -nlʲ,k,NZmV->,68z8fAdA+|+k(+pj6,vB+E+Bܮk'8|m'ْ+'dx -іk'(C,B-&Fmnj.R莬.Ēk+x¸ ߾nڲmЮk(n(+|Тݎk+&mફ+¸.oB&Hl䒫,'X֫~ov粬^lo//zk+몫&tk++¸n(BϾĢoknB/,slzn{p + p0>Np't 0#-p ok'쒫 rBlACnBF.z6X[6xwwk1ksqqϱ۱11!+2,1G$S!;2"g2#; & w2(r"w2*s)')2*gq,&*r*-"2-,2+s/222./3ίؒ8nߎ 3;qp+&p+<1?3s2:r3;3s#r3/<#k55ߦ7K2s23<7tgwp D0pZ4FlE4?rҰmoA+k?oqG.IMI4;^nMPEcjtQRnS4mJW"4B{'-M?@#8$ocNk-'xO۬V(C )"4`uaGp׊8/6+m:V837slb3gufs+[6o6`,a6v hn޶*5r+qsntvsluu um3wvO77wޚ7tgvW7w wozz7ҷw678* @BxI7xg߉q"g8L+x3x0~'wɆg,Bxj몇CkO rJ*Fj鐛*%OTy:B9W66*XjiSis+*'jgyOcyC*+@^8y8ۅKS[xcC z&(~8cI9:+Ǹzv\+zszcɧ+C:+3k[k2y2zC{:Ac4bzzDf!I<2*y fB5A L#kK@ȗ|B<˻|<>@;ņ:X&ADS\^Z{;Y |:ٻ#<m} ofG[@p[]E9bwED"DY:V[+">=@DFOX,?D>%?y?@ذ3[64hcD HP-Rd8B݈$N:1\Ipnđ df5de 3k\N\`HV b9|%B\6DeO#ӌKF];lYͦUm۵hƕ;nӺw Wo_l& ȩ&;,d*V!c/_ʗ1;ߎX݂xר3;5PF)# aTKvCULF@!6 Izf:>-un_5`R(*_nU:{a׎v۹>NL1[\|1H*i*CJj]ZC=", k%wtD)D3bHRA<-BLƤN2:ͻV5R朱N伳& . TG!A N1(f;A%̠MB+t OCΐ- ehCP:]C B$+hD$.Jd%D(.QS<WTXX"XE00 ( P8p`cqF8Ps̡XG<Q 8V@A h  \8=*KIA򐉔`$J!Dbd8ìP J1A,D.e\`(DPTDYBd&sdJ3 \ -JBpRlnܠ@s3{ kfd`8uJ </%qvq` 8RB0d9/_(})LbJ"&51QZP#IoXRFP+mYJ&Q$PmFP zPL)4U <(SR9)* g+IN:>jQ(6fTYAbmeKWT *׼%$0%$uOm`T"V'ˢ0Q=Hg:!_uK|ԅNЗEhW>oqrO>cq~޿5?s_/o/&uO ,fDHF,/Vox|Rf6dPo90.YMyATY]aep}cn7V}D& 3E { P E ; G s /E 0 P E ې UEׅ E[nh ST:%!0Q]*U2QO @[.1_V6qVJPBq_5dCvoeql$Bf^afo+Pt1s!p 0QQkȱo1QqqF^`[ASq`& ueqMQ6Bv!5"c!{E#`GE@ Q#M%Q#%WR%c&gq!% %Q &&$Ӈ'P' (0!'{erW|Yr#*#%qry(P)k+*eD[ $R- .q )P,w,{,oR0/b z/s/.1)().11(! H 6b)qӯ4415q65ks7U7qQ75y4g8{S8sst9[7C3ffF1239s3R20%4 -=>E>$ip- ^>Y3\s\\25s. T<˳3<@AA2+I+@@54C94ASA=Bq?%@DSDsEREBMbAHMB6 J!JGbFd$ @S%'c1BT Q#U= !$5FgP 5N]BQQUTb Ҡ!@MC[a@5*c⢢Q"&Jc94XUDrScSo0 X]&%Yse-5[RwOQ##L7XÏJ9&F` [5S_wjV #\B[K^5v4wu-d@^^ R{5]a#6*^U%U&d:]s8:38mseue6c6:;/eOfe9gQV;yV6SU.$RbXA`Va)h~Sghqcv$,<4DjJn"'7}yD4Bj)"[C_uEBu&S`c[In5wv-7=5$Gb5 !Pi6 u<@V?#n#pc_twq4cpSwHt|cc,0?bu@@XdG`DqII,CICbJ aDV*@,C`y{~$dxy7*l$Ks,hFEE+7 Q4t++,eTtB~Ge@؀X ~sWԂ9 ҁƃW7VBA4+҅CC؆5f 40fY~ucd3^}C=AB#D%eӕXsg6q6e6kqfƌ1;38m:g6g[9xhis؏g66'q:؊[X!?t82-iXFh$-;74[ٓ;tGwkՖ'חVV#y'vwوX+ +EYݗכ/oԚ;x뇜 $<_Y]yCVY9w]yCT{/)S'kXmB fxkyKzf؍ؐIfaZU:qgmZiQgkwڥky: ;?:(-D͹3ٟ홢I0a١3 ioۚZzzŚz9٭úsyw}ZIٱڳYezFO[E^ 89w]bv'{A;mM^e-;[[EaZWFeۭ;j*:3ǤoQm:{:Uֽ$Y{v5O֤\Sf[¼Rÿ[s[lY)۷{P2Z{[_< {={YCYGh۪ܴ|<~5Uķ/#]`ɼ\<|ټ<<\}= } !}=)}'1/=97A?}YX!QFJSW_gqW=9}yըA@L ڼ  ` ̭@ ͑$A< NaN͡] 8N ͣ߼}̙$]}CD]I\}Iٟ=} >\ }=}%^G>}Q^JPӼ @@ ׭BaC렣&~O!MK]ޛ̱߱  <|<|=Wգn֞I >ž~|˼ ^|Y! ;꥞1]59Y Z^PaQ-{A!.a/<  _y_/^ͅ/Oa]$M?_ɜ?"?ͻ_<雟?́A???|NVu < S \)xA,bZ1ƃ`)R<.(a+^ ΝbS mp ViPu^c&1Y>ڈ|@6(蠎&јdF6YHF97dXBݓ">8dRn%]TBX}QXTyeK(ulZTU}DjyMI A*_8~Mdzѥ35 ߦm5**㌪ Tdծ'kB᭸ٱʪl: -gA5-BNDggR(8ԗhF2-gfFin`qb/hTYeW[P60B&t0W ϛ^6/K妬$XAˡvEɶ0;+Zmbȥ6{MuB4>Yf,m gAh6?45qpd rP#Ϥ 58RSmKvl 嶬jC6uؽyJk qN'ᆣ]sޜVnjgZV/FfhAayRr#nzf7e^{];; YWf*/;%NGgd.ko;{?<;㛿<;eo>仯>??{Fs?x#(h@u p lJp T / jܠ?B v0$,! GhP+l ʰ1 KX09ܡ/ AX#*qI\D!B1>"uh+0` D Q0Z!3qj\#ݸ6s<;q8H1&pR$hH &r\H"F҆d-BQр#&_XOzRl$e)MFT0d%)]HABdaI\bR$}I`:R$f" X l[ќ&4#R֬6in36f5 q3-GNus4`6ubgY͆vUbi8ڶVemk'x'}dmm{[I斃ao!epY\&ˍq\F VRؽ$v{CRayHvUv;]6\/{woE@"f;_.iTprLq&|j80#)?*dq*Q\[YՀ2ΰ7 +~؃D,Ϋ G-*V%KtUW|(˴LEle0cyd39c*e(y=rPezs'f:sNs DPUsfGz%t }pUX<\.MݞQյwvo;fve}&{ў5x f͆ugۺگݶk mJvIqƞ7ZsVVu-m|V߰xj [x2EdaaV;ࣕnnmDB3[rb'l 8𦷳妬qen8ܭ//2q [ZW4g¹k3IoV:]Hcڪfz.ֱ_f{U zg/t.}sK C>'eIz'Krvf8,`w_+}u?W_~+_T ~{`tހz7}y2 PtN@70@m\:[%d@l`@1&1P@5`d&8|&P I@2hyāk[`.Epi8yXqKd LP@8 8` N$@{0{45P@k0!(8xXr3Xl##0~gtP] e@NP T @lX@n8iP@iPWs@0t{V| Zb@fP` %P@x@}@@ȈH_A@X@@XF>' CgT|9XrI $#$yD#@wx@h@؂܈})&$jȎet#P{%upWwoxwzWvGw_ixx~Ǒ uxבg% Y/鑔S#X@1Ԏo@sgHh@Hl $Od!x#yix v(IXx 7d] =Y@?/hZQE8۸NJշ#`#7@{D ًXH|ЏXbAɎِTsD}x_ X@Np$iIӸ$7J1ɇ5( \(08#`D3fpQ4{T{RWɗ_ \X@`y;Fn9*T'+x.1h{IXK m46M8LKKIHiFd}$ʌI&j*J!#yrY}6:8( y:*@W> z4ZLHzLJ G8 HV2JBzV{LF~,`j<*FI#֥1lZnzbV>k% c*=JAjçkw'woٕ2p)\y*vZj,9zpg:yx*vTTꩩx7.fzMr97PSJ7ˇ:Dz!7ЫZWzWΚW G/7{pW$խeڧCZWUծJE +z- YY Zʮk뚅 ;g* t:[NZ@T 0K 8@7@؁nY]ʰ:5ʀJhω@GX׹/A#,5(47LSKUkWY[˵]_ a+cKekgik˶mo q+sKukwy{˷} +Kk˸븏 +Kk˹빟 +Kk˺뺯 +Kk˻뻿 +KkNjɫ˼ +Kk׋٫˽ +Kk狾髾˾ +Kk˿ ,Ll  ,Ll !,#L%l')+ p 3L5א P02l=?|.\ ٰ< GIlװ [@FQ,S\T[|pF`G|\lgYk#8\m!,222244678967452222121111111111((D4^0$%  $#D z|#v &D*,../'(3 27 <%%A,,F-,K--L00L.AJ<;IMCMIGWBF[@t>|>~> IORR*Y}6^~>bG_uRTdST[YR_VKfWKlXTm[[lb_gfdledsfgwei{jkynorqoqrnwmn~inoprrssturvuvvwxxyyyyzxzyzz{{zzzz{{zzzzzxxxyzy~y؄}~~}}~nQ~ԘƠ¿ǽżԼҧ؝ڒ{vザԐɪȗՔݛӜϪѾH*\ȰÇ#JHŋ3jȱǏ CIɓ(SbIJKF";OILǎUOHuO~ }f^Ĩ}T'NhӪ]˶۷pʝKW`̓wEF$s޾ TJP ّz:PBEn X1jTuR=0lWg'5uװc˞M۸-;_m\xYxfJ)Qh))5Tך f'ܹGGG>{խ^Ͼ7{`} *RhBuxK$SJ$Y)9\(]Miၵ$&ʊrUwXv;`EcP B(F MhRGTA b(0|\v`)&HPG{OAflRqv0d ՄEUXYaщw8ZS9=$i1Wx7RqTi^$$RHb*JPScj뭸B.zFeQ p,ggiIslT` 3Y2hre-~)AChxZ߇ZWcT5ji?>^TRxuT㦸' +IiX%{lޚ*гN8>Ĩa *g!{/T0f5Ui,۳Kk>X ũL7ӳ=LRp gVڏWj(/CcK!U!-_3*dN(hU6Du=~h7ȡ2}yPG.WnzI TJSBi.)- "y~"(Xbb>qEK:Mj$;dR/f(Ο'B]ܾ|db#N;䖇/3K#4mM'/oZZ!GU HL:'H Z̠7z GH¶(L W0 gH8̡w@ H"H&:PH*Z-LMBtx` H2hL6pH:x̣G7p4I HBL"F:# IɏpȤ&7Nz (GIRL*WV򕰌,gIZ +K\,0IbL2f:Ќ4IjZ̦6nz&'Ÿ^"D&9v~ @JЂMBObnR)@Α[èM( k q.utKZ׺ЀD%[ M(ɽx ]|Wpnx|Kͯ~WjȤ[EjbL;8ip0 c9TDs<#o:qxCֈn5V0񋳋wcƫ1],"W,Fl&;PL*[Xβn0Њ\N+3csav$a*s#!]M\\k4g؉ѐ!|LZҾǥ7Mgӗ.@iG߃Ԩ^tI:հgMZָεw^Xq,qE3VZÈ(^xBPy<#ab2&T!n YD06qb.3wD`U$)Yow):a|p8=L a`쎆7G8m'R AX"rA" V?2F!L@Kg|7.]D2(0ZhT#+ofr$owsVx;߁yLE񘰟e s|jG+p Sb ELa>u Ia HϾ_3^}GL ן~}SD 7~;߾O~g}u g`G^p}ݷf ~|g 7- ؁ "8$X&x(*,؂.Ȃlpxb`exɖ`Sð ͦm'[vym& ;f=P;l# 7R;CX`2g-{vVbހ^P ԐP i؆ Ϡ pŐz|k(s ` zpH pi8 ex sԀ}| Ԑ w { `ȉhux(؋8XxȘʸ،Ȍ` P 3Hxoa v-')'3vgׄ6jvY;vgP `g0%  9rp V[@{y I!p  7 I!  !yaT Y"VI' Ւ!&i Q6P 0 2_&fl@lha`c'El(lÎ=) Uu{nh  @ .i  #Ơzi8 p!i }YA铈^ &2 I Pə : P@Ci0ÖTgN9QP="mr&)V%V Hu` XfnYm_َusm0 I( && ©poiW"0 0 p `ў!ٞ9 .I #Yz ɑ` ʐ 9@ 9yA  P pHIJYȢZ qB"6h'f pfIgxɔ=B@dY8NDP =Kڢ )3f5tnI(DWfb6  0 @ @wZ pP,Dd TFɥ/ `ʔpAizQRڦ \k@\𩕰JA \Jx^JlHzAOꨄZ0` dڪŠ$&O#1c*0Q J_xMZA&  ʀ a@a _ЮRTېSZzگ&i_PeP Q R ǀ ʠ ʥGxvQTťhjaĐ J*Kkk B5p 名 ʱ Z p\0`QŰЬ @ A& ڪqۭ Ne 0b cp L @ TBM&0 hU{_%۸efT_ppưʐU K˭[kA]0eTeЫڶZT0 z t @  հTۃ Ͱ TK@{ ۽+NU[˹ [aq{zTe`z[Tϐ @ 0 `%` `  T av "lUE#|† [g\0Khn6T ' ۿ_JET\ p˳  2j d9 (j%< E̽rUSE>0 KM% ֠ n ՐTm ݃G=LU% p舎膋 eJe  :,n@n @ P Ę^ 0 U0NP.UXu ˝ SE] ʮOeN?QUmVܞT۵]Tb^e Tnj P TɼRx~h@\h\|.IԂ?ՄT\MޠLTh;TɛL48 >T>B_UNX5ѩܬ BNUWN qhTޞ}Ucm nخcr_J% &ϥ˫ @LS aoư\\ hԙ/՛/ T U5TO"ߋk}(@n 0T͐ м`GP0NUFUU pUZXU6~ 0 pU@Vp oTP1L@a†MBo2l2d)È C(DC4$Gz+yRIT\@̏Ƭ݌McP)tMsSTTeJ+Gr Vخ .Djc0*[vfJ;p^}X`… FXbƍ?l4iv#Zz)R:v [f>5\WKej֭_ϋu!/Xx7g^V80k ܽ^{6g w/_~/ ^?}ҭ|W}5ˎSG}uu%ユ?3e)beܒ?H“2%MBI<[D&\q 7EJFm(m(}ޤt +Z2L`& `R뀶9.+/1/8c7cȤ24]ٌ6QyU·Upj#?Gf +^ZiRF9hQ.iX`\*; ![cCUUn)p5]a~#\h"YFVH%0HIoaq\oZTwzۅz}\s=x\YsQBAmcJqvayQm 4g{?a)\c~tQH5Y'miH6 M1mY0)捐[1RȒdq1׎&.yr:9~.ˑP(pKxDM Jp&6+YlxWbEOdhg Tbe,盧bhE/Zm FB ^nȡY#TsT5}Gv4a8BȬaKzek k*9(X Y[Q #y #dB$R[JhzCP.*\ACuE3tӔEءٺ^~a(+qѝg<3dLqt>:P^$8Ё>ByWRRb8nxaQJzcy2I-l 7f嵘ym'AU)Uuʇdlqf,I.Z)z2BohD7Rg8a΄4N2E4 xPρKa#8(WNJ fLg`;XvL_l=CAF3l̚-ޱ8KQH#aDt%ȞEHFC{[w<.r$~Jv`Z-8S´Ѕnr+U+yhzQd`*0B S+Ő;ޡq 0Y*ʓЙf׽宰*,7:Iaq+'ڎka`3*V T"Ucp5l1 E9| 2X$.1+KGD˞u,R:6m6 e.aj F0A*"Iv. db$.gF X>Q1FA ibYF>WcD=AFt]bLяQ S_5iNwӟuEKi0u B-`w`ZYltTPPbP%Vq r1z/ˈAe*O#1|ھ(0ha &{!C@U%*ml̑H0 A [ԛK~m/2qT8@4>bl%hF1G eV8P4>XQ|>@5r'GyUO ]ֲfsKuDKN%-H->L~yzԧ.uQ@"|Wע32 |oKgH`QcLiKh9q|G|x7?| sa:)wwqtse]0qcPէɻ)HּS?|=}FL_|70I1}W~/X 6<+}_Е%w{|;-c?+=93s ӿi AQAYH !$"4#D$T%d&t'()*+,4Bc@T0S@ĄIP37 Lf8;<=>Á@Dh8.TPA`< H?9$@3UM 1OEfhDF?+PtE \X"[$]^_`a$b4cDdTedfE@|EEdSLE? lhF;E:Zr/tTudvtwxy{|ǺVTEm~4HEǀEstHătiXG<|Ăȏ\@Ć|eX\L Ip?|'L.H9 t H$JyYGdehj|H2h.J.h0`lI.H-iP LhiJ8I,J[ɾʿ4w:ʅ,ɕ`J\Jh 4+.3E W˙0S IxƀKh Qz K8I(h.cJd|@l?ĢĴDZTʅh̅ VL//ox2N/XX\ʽL͗Ml$ə GPo/oHѤ8kNLpJT./Pi0f(hā ߜHROSOX"΁fFbNIpi PQ0/i/i/h(O3@2p:|Ąi0Q/(i/pEΥ ==IpK(E(O4NHlJ1Nd>5$Գ<ۤ<4h.2SKJP'܋d^cD}NNNhQo.QHlcOeQoQmZVK Rt#lmJ&} '=?u | dM;5ƠE.(URQ)R\;E/l];SX@P8QєIRX  G ih #i.E`2@Ie׬;uFXF$#VxI.KY= p dhcլVÈHZ$mDֵeuNlVNNLxѠ2PkTW#ϙWphWdX׻W.H=c.ŧۣ l,M =$< 4%Y]. ]0`t mP4}/\-Տ׏؁ ZϮZ(י[E  a]uĠT[,qۤ[˘VoSVK֨|ؔQ`f`(Z"*vM_dʌVKERf*\d0V08X+ݽKXbSdCuYix܄C5|ec HQx L8<K8.ş.^PXHfCF nV޹03N5Gss,[DC %g&g; qwf=7q8zHt577._Mou*r4tnD^oTä>FocGdWeLUvgW(ϝi̔vjRjv:kjOvcsGw^vhOvlnSrRlwxmW{wwzwp|wt'?wv?vvrwoxkw~iug`uf%v\CW?AGC_GOy7Gz[w̍_CW/qU i(GWz/Κ;Nz &y8{{7g{ćIVO[{{/{_{z7|[t ɷ{Ga_{||ڷ{9?be}?W/yw}~_~JIX4!GCWgw/L~d4,h „k0l &Rh"ƌ7r#Ȑ"G,i$ʔ*W%LHѤمfIvN4@)(RG)Ԩޚ:&իZr+ذbf%;,ڴjײm Բ ҭk.݅6/.l0b1YsE9"]JeʖRnTnG.U4VӪWnuէxgӮ}W/߽~73fən^{vy+l׫Qq;.u<׳o=kk>~۸s;=? 8 +q6St58t FSu^]Wv:JbBYQqX %ah9-şn8#5x#Hr/R%Xd$sd2=!OI*|QɒLO XHrevua6bJ&q Pe'8yXa@Ơ<:~0vߞZz)S/?~LC*HrT^$I AAZP+դOfr&jInn,.k Ŧ(bժglzS?߆.Ѽ3h{n( {h>m2#E # <0#L~*c-B*:BŧNdR+glf +VNk,@7wg0l3ys@H2a0DAkJ'ď0{п3]{_/A"F[Zj%AT +Lq%0 7#EߣPF<) WlThT$c5,- 챧E*nKtB #лLk3Q#L,usAYk=J;d -`j߅iHfT(]0rU0`(# }]UQP*Uа t?8:cn V:d"-l!L;mwVBl! M;Dh<Ё@xģ82=I?2!ō%T'j'hF,BU*@ȷ2Tb`+`gȀN$@  Tpí \׸F5 l%Xhp2'Ahl%*ON~a|%,c)Yn[Vc&_ьa~0_ QRtf'kQӖԴf5mr&8)qj3n3\:y`Ġ=`O/ӘG>`B#Pcz|#*!L|ccn1Z1ieP"REXv$$C@*aH$ "i$)GPAMKaJL"H.ԨMU8xPr-Mj Xoհz` -Jqbnu+zVvF1׽UŨF)lm);S0֭]lc*R,f3r ,0 Z5\\0iuB`0taxa}Wm.Mxm[.t b_82k]tɗчgN֑iX(!2I@NwRUT+(1TVԁ  f+A h[n(,"n#,auiz +bn"0lnbf@IM% _i+n1D4L 56.A# $$"Hb"S1K+!H@*v#;QĊ3@ _8RQ 589 L"^AH8yS\"IX[oͶB 6|XDt `91NR!B {"݇}L(PzG4P!IDET@G# r"wR4s]$AP;F4D(*의A;IC2d &CJ.q9SAx& 5ZnpE ;t+i]+4m"7^31rcJ-Wo g~F=ht݉7E4]aqXs \ ̓.t01 KN||{9\9;Սu>k 64o΄syYs t9~ܸ()dK?jv$!ic_iLgtp}km3:'# 0"BPA  (6Q; ?㏆11 ? _ş AEmăM@5_[%_ @  l}_+)`   `a >E:i\HI1\扠a}py r!!q !_a:R!iXHxڡaF݉Z"Nbs%!֡'~bbT |bw"",Ƣ,"-‡+ʆ#&>H;Km.4 CQ"2%zaa'4R#"b "7Fv#8ciT ̸AK 0 %TFTN%UV%U.%SbUnVnCTp%XXeTjeieeZzZvKZeZVZ3AZV8Z>@BPMf30 f>9#h@K&$LLjb4r"QĦl֦m&nƦ &nJQ!y+(qq'r.'s6s>'tFtN'uVs*gsbun'w2D*x'yy'zz'{zzgx }gܧ~'ڧ#M}ا/g((X }&$Ч6e=J`ef$t-<pi4 B_ pA#jΤB+ & P(aB+ +`BF`)&@)!(>+(E^$2"c+|'TB5)c u$:v$A@XA# jkMr͋@L_L __)y"nꭲ*Al j߫ꎛ&+"i*)xJ$p$@M$#0>с 2@&>8* B¥kꍲ(0i lߪ)!l&"f(B B(LA'ʪ6,biH--ɾG#wA)ά{,ȉIQ$= *$ةP-dkb(Ck(m5+V 4` lȚ- )}`B)_*hBJ ҁ^(h,_B M`ȎS,2XT@Kn!!2 ,2kDmVZf@f@_؎--n  (bB"Nllln L]ƟÊlpU(%4B187 DL.+Ij.Ep.s̃VL.|Ba0l*A N3&)ĪA C| A ))-`O`6_.2xC4StA+UL.H\0 "GZO7̃Sl0$J0!Eb Wo&`-&c(A)$($MJ,*A @%DA8!]1 4A8A$%4 4(7DÛ|01p{)FonNT<EEq^ K ; 2‹Q,B ,!._~`B !)"DG4&)`Ȳ>! / d!́(14+B4%P(,+3777쀭__:fz vjj ,C Tٴ7B(DTHBWtA}-G`a;Z9]Gh{s>>;GC>qp:08B$ڻ?Ys.d13|"R"uh$@7rwY;_{ =o;;a(.+C?&Fo+ձ۱w]=6ܳ |O;xa>F1ǿCFӿ?UZ+@A@Ja| ' !]TxpǗH ͚.uQJĔ9fM7qԹgO?:hQG& dĈ- GDd%+BmvӦBvȎ;h֮KvP0ōK)X,[ ޹ax_ɿD9I9>GyS!oik4[U*51fVdYCThBn OF7^Aܳ ?q-$G9u5 qHHQגًuS<QL`*HV ,LS&%ŊޞLe4ck"cmdnztj:>a}؃}=|ٹ!H*p$>H|li%?ED:V! AYqEVX*C]\F0F%^s)ENby/# )*Z)b7`_dMxFusRi,*C1S8>?EcKU? P֨$&w 8a]$11yHc8@lhYx;!KXF*jyK5ME0)>$B#HgL!.)YHAE}E5WB"#S(>L * s dYѺTmu[W7]׼ޡzk[G! @­0a @,uc3XNV}e|ԃgQЃ&5-:%]Kp[Ζ8moĭLj~\ XUSֺu(>xuywk6BU /Ck1Zu/BRm-lk_wPw%{-Ʒjq`x-Mr] OXӝ.h] k0@;(~ؼ!LzG^7Ak_5_^j`w] G Mv2k,ujTwI\iY>Q= {6heK&+IӃնBYƶqPKMϽV#]iky{޻ yV ]dwn h;W gsVKUޯ5Ar JF:fCV-1*~B\m*XlĠ|1A?2t/2fW5OԱufݯ0v3x}q'TuLcϝSP7oS>vZR*vZ  x~K|қNB*DuI }>Sh.!Qmgֻ;zCש|KR4VG'S/%3trit߈zuc~q{wEu._Gl. "p*B@*".o)\>]|ǜou%@pF0.nȯ<(dctpyPF/O$#AL-JM OLm  X!!!ŀㆯ H0H\n|-"ILT0.D/p '-/z0In>8)o8T 頢pp qQ)vHB0R%BHhע`PǏeѶdB ^'7РQ(qDRC9em: O oo@VHAeDBPp9.l1!2"I*󍜖,8R$#$$)$ٯ2a4 Q`n'sR'Ƒ6n7QrG&a U<1 G߆&$ nlod!hqR"2-mBxH@t^*$N/.*.%%7p1f24jky' \>3O8(ϖ)83!*?,ȓP "> P,U!P8-=p؈L:ЬQ3¦^ 3l4kyp ma S4_r&9?9c t@T&V3SULaHB({M 8Ð!a 29MS#9Шs:f83^ G3$s=}Gc=T&te<)sIqI3!JS?saB4mB9 98@:2"5'ta G6*2:T8ʣ,BB@DDDSEEadtF!;Fw4S>z;AX_^e돸dߝxehbX { ޟ~lYY_언<:O:?>v>۽\a?!X_tb W,_M9#DˠDY&΢1ƍ}ȑ$G82ʕ,[| 3̙4kڼ3Ν<{9"СC9zQ89}괐T5kիZbd˚vZijۺevZts7޽| x 7ŁUUc 9ɒMfbNklĂ|WYw=kAXFjM; Y2y:=ԫ[N4PH(4TN'o|UŚ}Om[x-%lk|G`_`USOFaje&h`d,PATS mBu(K=ep } AU$Egq%w]>dBivqwa5zޓ[y|ɷրq7] ~^Ҝfjfn&)IgvމO,SP\&Ki[1,,!(NDA6ȑG:HsDjjK> kBzkb ('K,hll2lV7 %^ߴ~k.\Bܞց讻=u=afhs5-*K97ȓPJ* 4uZЧf#Hjoq ڊ&nk:+,\V{mjUO>.-sn>?lK?H(6 bkoZo]Х'EDA,R1r7 wr؇*J#l2ʅ,͠,,L3ʌ,yBn8 uDMꪯ73]:ON^{WE(׾n'@M?}?AȫoX*'u&O )J䪨"l9ꓯ8Ko~kӎnXb, kǗ'hh2*XiNw>;}!3:Ц<]Tk _(7M4UL:op CWA)gD$*fE41XJRc*Hf bb*_Wr"|HAh%\ I;T) 0:c`}ljssܦ7 N6f |cytsl; t^%:5ta?U`L t&}NŠ t(Vd%(rY,'X--JEp'-: [b 2̘F󁤨)hҼsq&O 8Bt8x*uLmbNtW/Ug37[Mk`ֳf' f[  P;FG{6?rVVm}{+qo˚;l+7YRe+=.먷S _3[}DŽ:-B2ba $G4 4 xvCmf=ɳM&<\%;Can$K^ɓ{̟6Kߥۑ:(Oԫ~o_ؿ}QQ~~_  $ @  _ǯj$!H3QH̽yV);HJR¤8IJx~x2G^EjQFaeCyEMWGRHhȁ؁:ʠ:_`'j')h*x `~@ ^`Q2QϠ `4h P _ Puh` °~ggW*G'Q1Y!O}1f@m8@ (V_Yzy;tuNTwLluk$qvTk`w~ NQ8Tȉӡf@@%C\$7  fT SRXN!( iksprtXMxY{ht6fQ YǍ獀plf~q؉h;!#- $<] x+MrZ 9 U _a{w茊!yzȒ-FM`Ѡkbv(]Gv00 nkr'vgȔMy**`A!-9腃0 90 9]`yUa_\hIOіz*9$X. . H vTHNP9*AB0W)ݑNQ I@ qi Yqyyⵗ}!RcmɒYT9Kɘy Y:pyN;%P*B%0TI_sɚgx1iatzDJzS)nٙ<`  `%`m9t Ui'ٙjn HizɛKןGUIEZ9C` ZYg ʢ<Qʕ* % wN! iOth _ X8z;֣?*~Djz:ϹI RA ZP Uh,@ B 9|0(@j  ઄0ozNaj3*˺٧ _꣄ZarZX9T$k <TAt,hJ0#@D0*0MP%0_!0GoNw yJYJi ƭO^A);J-{0(*J ,,CGQS0o!@а __v_ a+cKek"Xoqk˶mo q+sKukwyq +KkKJձf!";j&[|H@|0Ak4N<볃@*N RK;0 kNjɫ˼ 뼶@k׋٫˽ +K۫[9H˾  0 Pl pmVC q+$ J0*`T@w7 9 wi`*г?AL ;:mxʬ?Y}{(uSkq@FN Nx~PaU GaL`v {PnXnBNE}pnf:kH|~ C͓0Ű\<- e}ֻ-P9 RQoٮ A ;;P-((Jml 0Ð=يn&st=uUf NȎE` Ϻf k0۳ ֱۺ CI(`n^] tiXZ^Mil^7wF0T2Ivh֎8:^ qeFt'LG KNscRy*ܷ*Zv@} ޢ孾5MfL)FyPyIn{ǎɮ^N0Gٮ\Oqw1HQA-YljҪ> p0| DpR0$ʂ(1a& &jQ&pbҢ -4H$D)tI( lr/rK.2:k-2XK./k26tM8sN:L 3<-ӌ3><LKN5>tۍA@6 tFSAAdQa:nKkLYAK?bU,jDie>x?$eNQ$hT1U<̩&z€ÊT0ĄT`!!UCzBI! #GB(ܠ!H"w2#Hsa,"S-3Bs5xc;c3=9tAU6~Y&R.PN xԜNG5ofu]LZYֳu/ΙΣ J2 B ̪VX頾ъAW\z5cdB+BQ`!鮝Z?.i{??[C6T/i8 J@ l1fUehjD>NDNhP#LD@ҽhX&tC(t5cd!-BB ;C2#6+6FbT7Clݨ;ChC>"@ kDf@DA9eFtтDIlDLt>cDOEQ>R@SLV[EfhV{XdB_^T_8b|HLcSFȦf8;FiѰFA|0m<CkP GCt7sDQG@`L<> k@PoLн/@EwGd5<YœE/kċHۙEHH#g=F0@I,I9Io$c0iFIPDG㋲KIHԽyDB$ƺd̎|WIT< tT lƬǴ1SDEbN[i1tZ,\<] M,JaTM|O8a͊t͋dƳ ٜMMjAl<ĔT4.@/hD!NJLlGK Uf`5ǡlce```NVNXtJjxG ODOZ"OQ`* @3t˪s|g $„M dEIZYl)QaɾHξX 5jS:SdX J1m1iS? QSe@D]?mTG}TGP>WFheTOTP UP-eUS=UT՛Q3,ܒQZտp/<#/  g;*?R)M Oï 0Akhݯ/Mp4/`H5M6 d4Bi94SAq5[95SxQʍ`([=:]^Vt[+4CS4祶 Y;8a3<L`ܫK ޺ Ʌְܡ]DF d9N_M4 VߤZvǀ`:འ൵``+`>a|< ^m3kaԘQ00;7 Qc U VcW~[2 5e?kOM> dfjnkqɾs_0bdW!tWWǼKL^5m`>KbeV Szgׁ4 hfÁۂ.KL: &nVާf96_vn5I6J&ydG+=gOn$i0&V`@iwR6i]\XѬ4Wjfޣ Tj`N~권jhE4>ohlShrD掸[PhPmnIF|Tkf5fw뺮iĸ8(UR%hip؆`U0cPTe(4>I+lvc|9&ǞaRl0,Nolex7n~^&jxjSIn8I>mgxGpm8mHl..6 fnk.q:lj{Mnjly`q?{qʤeoDe5iזt^koBc(c0;jJd0Fahpf/   ' ' /oqn>hn]fq]hlXWF…\tJtK~("?#E%ocWWpX`+iTOuV`uXPuXV!X !VhIhUb[uTQT8vfovg7Wpvivjg!5g8is W8^n;n< s?OwfD%+9s`R8zjbeDxBDF?Gj@KoxKFv^#;ﻌx[EE~yxd@3V(WrXX>ag)eQGfyzR z/k5Xs;vOos¨/dby7V8&qbQb0zN}Gv`w~ZWU;uRR(6b8s?~ؤ+a6̸޴lﺐ FdEZDbxox$x$c_G|Wy+VXR?8 YbǎZ6m!C=zl"aQ̨1c1e? Iց&LYʖ͈*ŋ1f-oapebYVӦN1smj֮bV͘i%Y2v Qjm7qL*w.ݺvͫw/߾~,x0P#Fhrp Kd8  3&S`$ 3ȋєƬAp94:5iԩWz CATviܘHMY4șJK4tӘ-V}S\/>|?ܕE<+c딺tM5VVhBː 3"20 ,, )BPؕE1I$FXH$M*L*$ 1t ] #N&9#08S-Sd=bsRA%UP%5^EW^%Ye2Mzx8˘Bgk٦s:S8LՔѴ6g4Wrmc]hU9-8xsmgSpک%ш'w*^yJz^4I||4h` `k& 2 E cD$jH-\.b+"/609? IdtT>KnפSU5S'Wd9~PB47VoR\cy= qz-S22TgM30M+-YZjIKHS4Րz77J<߮F3, 15  z_BcI;-Df#t˭ݢRzL*8J*1LA$͏ ㌼9>"9O-GSK? %M,BWU o M94͌SN9^{KE!G(K2.uOK5z63/Uir>D'FSWtҨ.mMj\R+H Ys_#lmkeDvv$`2WmDc& /Z!GPjQ zУk;܁wC{JTTcJ¢0.EI-B{]3"#"1J\"Px߉Lx+TǝlV˃U=/cs#6z?2&9|3Ɲ92|M[>jא`!RkZHX +9 ~DntSG8.2]ЈWA &2LxTb. |WA02!$CGr>ǔeEO^QEc"5ikf̉FV)leWD{4~1ifdΥ>`/%@F%01 8~xLӟcG>:ŜSKc u5H)l$r)Khۚ X!o (2P$%NR$T41X.-%'H1VX}rV`e{5Bј[WbD?nd_$7J4UHH/9ҐXd''? ʏF-Ph@Ǧ)]%G P{Km^ST^tT}U2D)/yV()2+Z7s[WQve(މNJqU}κRI=Gx? ;Au~c#+H^gJf#2<^AJVSH}Ě\t/,B/EO饞]X  <u%YB/xPԩ4aU5A_ m[7IQY8IU;B]>%ۄ=A!S$_G6VnDL+\ 2PIDCϹ;(E 1QaE/X8))͖E0 :#W vYbp J#{(~LA1FY6DN"E͍aaFh^څ2VX.724R4JB ̡+B7B8F@b9c5IQ"5TwCc=C>c=r"xb{\GvD11@Tz8@"C-Adq0D c݅1Z5tSvTT .3dD89LN:zSرWEԌA+VG@2AZCA$+"Bj_U1++SZUb4B+l%,dW1t%W%VZB1D,Z%[BD@B[)\]%[!߄1 I*IBOu6KRL.M:cbMzA`QP2PbGQQbR"$-fj--&j?tf1ьf4 |v)TCDDTLHV ` ya>[b4&dZudް1S#D_d&P^?Rg dh"@*%}-fԦAdSz p ]g4ՅrR szI@!tFCLbb( $JyIJ J P$ʢ@Dä HanQfr{J|'}6e@nFa#ŀCK' g_dI6mJsK*Pf蘒i]h'NVw'VG~-EJLTi10JօRizJNBO"Ty&hgi> ҩ}~2>M40("(](T:҅.f~~AX1PcA,(2kT4B 뺲2JNYCk֫j)14Gk40C(",$,.l6ڂ|悭NG⫾F,m6~(Ɇ+lGl˺ˢ,͂k41G𫺞zܬ)B+HZjkAP㵲 :m:%$&("Ų "ԑ7o6^+ģbSɲ:'v)bNhVhu>^gRf%Z%jjʪшm}m---h^ kVm-w8\@+nΤrgeծ宏F!"Ԏ){.olUFO"jۺ^I$f~)sH뒯LItgҮT)8/.B.."/w(o:1/{@AJ/^ddI`5ip:amtrjb#Joed[d>MnLjJH ?fmr.p554J{5CD 2 2Bʌ?[k[fxKGOC2EZs% `vaa#a1vc;cCv+H7kVD3Wk5Yx~\7+*Fn+͸25BY_0'mvn߶LDvoc'e+eff#7uv{}6K'sASO75j5`kv 5wu_ɳ_uKh:srw-w65̼tTQQ݃>=w/ 8xQEv4vRP.8=Â{C8HɈG5Èx{xEy'ytzCpSHR{gv|\B[B%P$L"A(B+yB^B^n%WBZ+)Ly%*ԥe-y™9)œBOy_.y:+h97z3z"BD,y[/(sz{)z:,)*|9^6y[$A#,"9WB%Xz|@ ;{LB3;OB#4{"[{%J#%\{{{%b(·w{AA%º˻[$d{k;_7{s;3;{£{+|ۻ" [c/·$p@ {ȋ#{:ɣ|ʫ ̽>㽴ާA=}>;~ɟ|<1|7>s̓[;{={scLKӗW{DFe(U2xaB Tb;4(VxbJtcȐv"TF`+!I GXB("yVqbO#ʬ(MR!I6utJ 8tXtkV[ilXq2m2hٶ:TRֲ[.p`?0|qbŋ7vrdɓ)W|sf͛9w0a$`L8B 6m{uN }0АsOr4lNю'_frhnL1Xs-N!B "QF*e 4F5rk,<𪽴+AbTAD-䪄:(/.Ī"^KE emu< F㪴R;obRH:`1O5vK7ރ%D80J".C/'s$>,꼄6rmR>Ҭ/)ZD * 2Pѱ"0+G,kR BHKù\Jzt%LE0w|XeZ3Q r!ԚLHKR%g;LLN#-|"/ixX 5YZa+iLiNTD >)A"I4+Fkī$ՊRJEWN9TPCv8UTcU[=E1\U+^Y=W\6!6x ٙZi8#ڶۖi5Ml t)ZW")z7!Ц Co_ea5x f+;+^N$UI>_\3C9+bY\`]j-3+׺<8u(]Of㎂–k?}vYt[hgmR+\1y364_p[t{qt؋:5hWԇEbq_F6[/,BmxhӷU/+RU1=!4JMI^60bf`ѐ /^&t2 b,*3tC!(@(iT !ް;/H]7_Տ_ßXv)(a !D 8A3i.xNn+lzܯص@7,$((nJSW),EJJ  XJSlc8Bd,H[FD|d,op6/y\V3|d"IRR2Ep2"0:@QYNsSd$M+ʅWRycʳw'"9s 6$~_fX(EhBD%Mh;QŒ!H_N VrD†*Rw&+ #҅!A̰!p15#V0T2u'!; `n +`) :.$:TT36~٭Ɍ&!\ZWϘtW(J߹U@BXA W H0`{S2!;i"K…P bI3`+::]]lH vWd Y"B _XZ$V uUDZ-_b">m ^SW*e\@#\A>J;B%ڒA@mV *8R%ILB "ԅ TA˛G7\Eķ5{ עR.C\ϥnX]X!`Jw^@ Tb'=|27-(`]H`@'\a$ڰ3;L5$K\1Fd*8 m\<7Yo,ey;A:@iy* z,P_A6fM-g.kq8g7֙H/I>'WֱlS讥 Kih7.p+a" Ƅȥi,L˶>e2@(U/51 # fYYg V n 69e!62d`z mC}nRMŭmxЭKs?oc+ prqpx)d`sx39Ina*oZ̗$qvJ-9Kʠ|BG :2D`q"p'M,\C$VGaVp#LIB|uUEt!_8)q3%_O;~y_|?bP33}1XK{_>ѯ~[G R}_OJ P  0/ oxo/6)#30 S0O[g0Y!sPw{w0PX`P pEp(p #M|.P o 0 + : Dpb- c 5PgP1"Lzqq1.@+ @7Q 85KOSQW[_cQgkosQw{s B?QU 0&@q(/@@Q q @בQQqR 2 ! !ϱ 1" Q#?:R5+!! V%_&cR&g&a%k'sR'w2&o'(&(R(}(2(#R'$" #7#E$D1MWR%R)ǒ,])-,Ӓ-r-.-.2)1**+ߴ$"2%Q,R1wr.1_1S2Yr2+2--2/*;0G3+01,/3[3#56c.g66o'58)=4)/I8O4)$%U1YS7'37,S:ǒ:3);W7}$48Ӯ39 s9q5@ >=3(>>>'?3/-#?3<<t̓0)25ճ9U&B+B Գ  @ ?3'GDkDOt&St;4@<@FOi0Р 0`+) .atLAMZA}V`ԧLIT}%T}ItJ@It 2KTKT۔PKMW FaNOtMJ KuJ ONPuPM5R4'5QO-KR;Q5S?RUSGQ=TEPSUTQ_URERIV[M So5TcT{UVVqNM]H@JHR xʀ 0!x[+j\MIAQ3=q=Wr-U R CQ @ 2x%`^uREْE9 : v&]4@ct#3\6U9A߀J qB@ d2 LCA dۀ CM @_ahr`VV:348bV$=)'/v] Z@ <@VdC. B@ X m[ x cU mu PB` mC`VrhiVWh(!7@Ej+7A^+V5 `9U X @l6G__YcX@2^+r6_d6, ,`pR XszWzsz{W{{y{W|Ǘ||}W}|}t_rSrer-sA͕sQ @fQv@x kYeY7C?6d_lUhSM'w%.x#rx{x'y [؅_gk؆osXcx{؇qX؈oxX} %"V#68dwwsaR bP˜B6 8Dׂg7h@ vuCXxyq7pa)RCEOk_v ~!Xt@ DvT@EWX BYKpҔE9g w 2aY~%Ea~vF#ٙie@#t]3Y8YcK0wvW iRP K}yu6vҘ_١_%Yg9Y1%Y-8` `=c$yiE3vZo$5؋#~{ڧ5 CڤqU&~PdQF:Q~2j'qjkaa}%3av!غ:ZW|A1%aű:(Wq[i@gZiZ$\+%-:yK۴KOtI&G"A|D:3!wa15"aQZ[`Z;qS}AѶq[y[ű頽R!#ײ67[;A{%Ees%Aϱqm!w!1v%;#῔'aE!;;O5Q `[|+%-(*_ڑ=r30q:Qti% {% hK/y<q%A @3a qͻ|&[\{;QC˅'7)FstG{GHHtXSQXsX7]QuV}ӇC=XW/G9}WRY]gRKe5A}e5=W|=|`ٗ] ڥ @ڻ =ۯ}ه` `NyG˽ݭ=] $A] S ٟ=ڙ}ܡڋBC^}٣IOpYTY4ҟUZZu[{*'əSawxqryL Y`'@nٙ6Xn5k]uyyR*y,聲d^vKw1Y@~zy "'k^k6@nu‘^`<wWdp _g"AtLv 2g @_dgՂE$A`*d _w_ug qС1S#c_2( L{iRnw^mK[&y[`Lݖ}'@N9'wy? 9 TB*\;0 A‹1P 3$#Ya`&I" A4j (T yi=rόq9-J@ KI!D(ANBrv&Jbee\i̤ hGk*/WI{EF"\gq@!& 2ˈR=CUZ($ }k*r -;m?i(Pq 1VC=Hqwy跷%Lp0Qex4ne<ڻ`sn|^T ל]6o3zl.' UΒ+NCݭH)N=vf݀|=y4-W:zvX'aK؄C؈P*f1T bx*dCdž7𗈱8-w@HGxG8Nøx|d}3ez8٘ 9fB4"yx\+y I ؏gYfigkٖ*)tipDKӔSB(Hh˜hNam8&SDž%3I;Y gћZIPi~aV$Iٕ 'I錓xijГɚ9(㧂1yaiLكʜXWA]s92YLڤ Ke5hp=I $#hh5Gi`iiojFlnO vn#gm r{nm` qoV6P6vk v*klN@ 6ƨF @ O*n` JlӖ Z vlJPvAqwqo rq*qjq4MLQ:qκqrZrvr׺o{r[ڥb"h)H ùQ٘ )T-JvdFiZ;}>ԥi*jh "X8ڜ.zGdJȝ8n$K;^*Dz3+ >+B'I9N:dvJJpJvvH@3ǴQUpy{h^+(!e+ ȶ85 t;(B#;D.ZF[XӇ'0Ƹ遜ria`j⺿ɣk?9*Bg:@g'F|k+-eO۸Ӹ>&;[[ܛ޻˳:z[k:Q`|M]٩J0IkN;xP R 6# lؔUm /7<66=y<|)Bbom+ הQ&ƢU^|T~Nd$]N"zZ\~<`nbޱΖ *k.> /,ˣ>٥~#yM)NƜ F6ZDa~ZE)/kQ5PR/?. ) R{!2/΁6_ 6k;{_z"~"0z0?n`wpZp3/vazPnpop_OTplfa|Py/П:`[OA;zo /n0OPTw q@B >QD-^ĘQF=~RdCC ѓReJ7ћ5_cSg"7wh9S(%3E4СwzT'{z#UR\qhI'B3hYs8V*y=HӬ0}EUVVG!AYdʑTf fΝ#*B坸C<[lڵmƝ{k'39)Ni}Yr9uUn^V`u9!=u>.te^~1-:Xa[2Q@[D0[1&p5~^(s _6,TI),J괘Rlx;H7o1Gw$}8[.9蠺ι>IJ;h ( /;&`1>z2z/Kz%O@VH)wAPX,$D1VӣERDI4TQQ%4ўkS'a2iGYgV[oG&"2R*4[ɥ{R:ʾD VML4/L7VO_%_A_sOAW0LAvA єe,BHW%J-H4]IiޔN^RM])jiuҔf \7c?H߆Ȝֹb=ɧ-ٗMS&i%kVۚ/&>m0UjT~H*tEX^_Q5.θ ߆;ne] H0F .Y%ULASHiWXDVoW\qʉ'\qRH-o]`iKIs)GTFADwԅ|DQzGeӋ y[e>Q7}{Vۥ^>=G8Ia/?wVRS_g>OYsixZV"D< npRIG8 S)"`zPą0KGk`6>$\BSts޻\TeP4 {̰.͋_cC !FH)N&eJqYtT<+>њV33dXiZ̈́&\nD~u]1yʦ=k%ɗ u5R3avA2:imc,e9YW$%F%XwIwBz5szv@MAU'$-|@jaaMr2R (5A4fňme>ϏҌɯv]9@hB-*4PcGsbI&r-2M| I%pA UdƩ})$0H m 1xi0y+O6թ9a4T嶜-3ìc,nj &zMB ` ʹ6P4W hԧְ ITW2ՀVU'ŪA4yuYnvѲγY҂e?*4#͛ 9k]#3^mUX;\W"U cc"^EYhjkf٘aslˣtG*G֐յwm0ҭ{+ۣ6q"%]cmJ6ϜZs.wg]w(ydrvZ 'FqUb#*7quc(Sp;d"sGF( @ O:TU*k \3)(!aFs'H mPs,&Y r2М&8 x&5AhF/x\3Zlv3-)Թ T4#M-xZ C ڰDկ~u60kZַuuk^)v=lbNA#T FsX OV%'Ewc#o՞rf/yfFu1xvg?כ˂FяLfxi:ZtujU2 3r'GyI|Ȇye~e7{LmkZ6M GdXNsJ_pgK+vŸpG;pc}m7)ig_u{r#8a|x)+{͝s.<σ.t d\:cX: u+]'-}v\jf;ë._|7O/3ˇs5)ow9<t*?]QuK> ?+?;?K?[??{~A=CsLs;8#@;C@T $Bă@2ۻ주#7*䈺Q4ЛA P%!;yU>XA4\ACR254C6xUC>;T;|@÷kQA?3tW`Q@tDJCK9XߩF*D@=QBahEWEB*ӳ;$y=H[QaP_LEFT(RxD8|F[LFLTU(qFT\*kF1( G 2L+GpGBGvL'utGz2$QpGrl{GSP~S":@\wGӂ8")*. ,j.4EIs۲ SD=OS=@c*s=xɞɟIA+sIk?BT!L3BC¨ %i{Bl@n˰dDL\lI|II|c0 7%7ܣ4T%\ (9t8<4JcʝJ ¯I$?uCK0c4+J;J[k˲:zˆ4A=@c̽\۴B̽8L3'9(ܾ)$ IAd7dM4Jؤ|=?M& Plij TN{N}5錶 @@G]PH֋%2mK2o2p3NE5O3~mS2uvwux yUZU}2]Q 3%_X-X4;XLֆcX-щ틀oت/X:oԐN%W@YY\S]YUK4W=UͲ2ٟ֠ Ѣ5Zf8gѥmB@m\ \Zntٰ3AUSeYCsٻlUyՙu[ҽ?2a[0ëц%\tKچE\+k \ȥ ̥ަ\DύM3ݳ]խ74]Q{M8=S9U4^+ %MmM⭲Q-h5%s\=2˭62r[^^Ou _E]M1mݘ}ݸ 5UƳ_b5V$Z6-Ȑ>&އF#X% @2$&#,<-Ģ.B t GIWEF9<>|C>`DmdCLhtDF|yXY6F=W;c:]0FmQ0EQL3F:t6-dGY@c>EF>)FU Mn]E>>6 d9%X%QeQ%Y9TFxG( r{G,ac>ff )f_~'#H>f;@\AlHk! h`#Fb%>uvv5?Um5_-= ]g3~a2 e~7.PLiei3vji2*j0j*x#OT=a(蓔Da5P^f8 ~>˒V6 l647(l΍7(쳖Mu+l7p8(l~O+lClVlTclvƴn64<.ԶFҷ~ָKmʅml%l%5FNYfnIMεfkn(Nکn[ n&.QVoT{nȌnCÕm F}ڊMo`γx&,=WpRu;;Q׮&PVjp p5+x+ S!PcK3  n%p8 `o0PhSs2#`"nBo / i-b)pm.gxS *+ Hjc0xs3 8:w;&#C + 0l6O<7o#'(O\ra,o-.W0ڏ-s+3t:o57Js5zSG0 t:8 8: h 5-3'8 v:PcWJ+K4;Q*X P r֡VXxِ1K \2^w8_u x;b?v0Keov{h&kvmWnot+v:w.wJt+kwhNQr^k}_u~r o0  Txcx:x xxTxusWvf mv.+gǺsguwrwí+ wW4)Ȁ;zK Gqb岊DEG(߲w<#G7Ѵw4y9z'зwy~t}܏s_H}'x<{z}܏2Ih~Kxi k|W~ޗ~7gfg{y>uδogGoڷb)B/ܶ+fG-WYU+S <80tQhX_UaH: .4HH@ bd*& 9H3V2S̆@Axb2$5:ϒBCȨAOp=VjԎ85Z1GNv tP ʕoFwmߚE jlaUMʼnP*ZFk ZԨΟ7!]zuhՠc͹6]^Ͼ 7QI5Eb)VINwocL.0pidh?=ӯo>? 8  *8 ^k1GW]DFaqd`A]Q!%DW0a"ơ068h *x *(:(gh>$^ez)M:*":+z++ʫszi!!f:(H,J;-ج)[۪醊-.;/{o+/򫄰}h.KA*0 ;~:mfm઻1rq8~8+38K>yS~9?n{9矋-K/ wݭ:R-zWq*Nɻܻe &o;K?{wSŷ_.#v̬S>Cl}n{H ?gO_ H*PJy 3()~F,@=/!CMȡLI"lQZb+8 _TP!PH V. ihCP<8HEl AtXE|h]pY,BXg$H<~ T > A9z"HH"$d#.LdZ܅_b]d%AIMYL((JR2$O$ # JvC=fqK\&CHHJr1]2$LQ:AZ<':HBP# B"fv^ %s z >OY$gH3 5Hyҁi"!^K@ЂT">+Ƙ\d[VAC>5:%ΐ $! R0Q M-E:ܡwjU}Za I-U2^kDNJK+"j"[Q Ki |NJaWJVmCZ9v})f3E=h`yàe )Z12P@GXlU7$mYƒ/'L] r IΝ^2@կe `ٕJƝx–SrT\I4${xW zjכz/}ʷiKJ9Z]z}l޽}L#h4NxSЪ=,SL&{`J0&4 ktP xe{A WâQXPT6jt!QVL&krV:hZ>e2]:SЙmfȂk(;Nf]V~f7 } _, CTJ@ i M{)A2WEײ>9MN{aj0r(=M DHıa0 "0c1%2r#6c]IB5j5ݺ7a1d`>j $3F3J"4.Iz]CX& Dfc]$GҤF$F$ dH3&I%=dp-%S.e$GvdTzT~N Od]$LI%XQ]T!e\%?1%d%/d%I eXe:qgy֠iBjB@Jl|Y_T؄Մcׁ}d2cb^&e>WrdFsekIXej&h"fgZ&ehr&y&jnfl&lb&kgfEtL5-GqwxyG^Fg:%"Zubgv*w~'xx'yygyz'{'{\O֥eI'~u^#n"@Y҈J((*J9_Ąh(cW<@ c|VVaWR~vgBrHPZA!̖2^QIΩ l) T(2IсLvN+V+AG$]_}}zbZ(HX(ҁ.zI, 4Y4 +| ( )[HlҞ=`@HAiX@HP+ A\@7)#P*iHf@oTũLl*6VHP.FA +NnT6h.PHU$.A^ H/6~qZ3Af q ,zb q15ne]!$3#n~m ++3--U䶠{I†z Z1v֫u#.n/{J>ޑ1gAE/^3cX@4#+6isKwӺnrDD3Oӌ9W"sF1\'(ܪ8IR_@&;_R_tĦuAKRA?|>@h8`AFTaC]2d$bE1BΓ6ADd@eK `ƜpfM7qԹgO?:hQG&UiN1[^б  8*R 6b;X[ъ|D iᢕd[ѤD(w| Hi{mސLHQݵmVN;n*Ӣn [AɆ "="JiVqch 4J-lysϡG>:NUXmָ]_,nA[W{Mu&^=GȒ)O-0$@D#--PS&: dMCl 5# *n^JaQi벣ʪ <O \ *O$ZP0L\P?&tɤ@$Dг.,4R[M$\0!7MDJ,$c: TA -4(YJ+ ʎ%dɐO,E2@)+RL,3J5]p M΋;IJR2Tem٥@vǴ~ )HK/U)1mO/* R#o\̌PM%:h7}mWz7"`i=SZ?m!.4i F}4$jAo4&Ǎs-Cl5P^)]E B4%_! ؅ňZ꩛XZTKkdMS.S*[~9dU;c9֞$6ڢ+*oVOT%n!?E/+`0\}_6BsI]XU1,ռ]w0绹!PBY}"\Oh?Z `>ȹ@1&4i|40 .YUL#SHiWV჏UoŔwɝ<Ѕ$ /A`(A`*L Qā`D׭s!A BmU =xBn0sBHA ЄL!qPEIr8a*BX RpM"Р e,X02_C !@$?b/?}#ir8IYHH #L r |xSXD]m_!(AJDRQ!MSb%N{Ue18ZVw1]`6[]gGէ0 퍁 + IU@6[dANfՅfِl `-mISӮh|Z+ׂdmBZ?MAjܼU=nA+Wy.(RX vv]|*MhTZМ0j5޲W3e{Y[o^Л&( k7u B@BSe-$Ruvk$)O"e-g HėeD!e`f5' wf99s:xƳ\f4ym~,эvq2Sl69ΗgOmvCFmf7a[—`% 3~djmTYЖ β`le/Cl7P!նvQ*||gC[, eu:e5hj?ĕNKgZͅC$&ыT? )}K2;H(us;nRy!_7( JP— 0Dy < k=&tcg{ۉ6qfq7v7ٝu3RfzމP ʌoJXs(la]9Z% 0x̹]p;ipw}t'N񿳽nη%4K7.]bDB$P' \{gB˻Da 3[J};xoYҿmo9_:so7^ϛdO}l4.2!(<.z>R/h`h",0/,Pazaal,BLJN@Ѻ $ ˔!׀N0v| o٪zA,ᚭذغ /PO/oOq{ N,:-nv{!pnu0 X*.UpR+/^;l-y0@HanìV`@va˄    vAPnQq & 1ĮsL0n}XK&Og.2 $I $A"A)2Z71$o[PjQ $-P|"0av%ia HuzA zLLx 1Qv"6 q-1U--ˬ. E r‡H |̀ '~^BG2G2s|̖_$Oh&H32O2-Pa6cs2k"3m+J$S2լ%3A99 :s:}7Ì73 zS;#:38oOvzG I:'-R.R4~@tCO.sƓ>@;SFm4ˠ \  ls0uirJ 1!!$qV(35me#A!ѮO1KY"Lϊj22M:l4AN=NS(Ma`K4%RpF`x`,,GT -q.A`T2M/N/R 4VJ/l׌3K3%nW12!%w%ɔt" 2&WNXXSlaNG&|a=U͂uX1Q[ݠ()i4]k0R ǰ3Ē rS TMNuT0UpU RV1hO2Wܬ 6,j UZS#-Y2& x(f5eAeYv6waewaӸ5伵}.:\A0\vh21^fLvA20USo` ڲu&!"EOmOobX\U3pkccocլNS-5ad# Mg'h)`[4VY5 r0.;N^a frΒm; `v7Rb./UpW7pqqipz : {rN9s4tWONU7QmfmMwWpwق/7jyVqqr}:2|/ʷϗNӷ")}?u).~m~S~m3u6 ͆mwbw//V̞7wۗ,X1L W?8i;N}-wtPIlY؅WviX[Xx 8 j*k¸». 2LJ=\*bk]Plf˶X Z$3 =K!x`VȌ븆l;LzlL a L $#-0lt y>8.< ns*i$.#2y=8CY!̢JbN9R$ 9,+YXZxˆ)ydl@b4ĘRl?K HYM >y z"gĹyfkn9 TL6 Y,vy yLZ3 A=:"j-Gb9ƣٖ9$,Ly>4L3~9.BW:Z19]yY 9 z!> I9ɨ:Yb{z)cF:}Qz0Z@Lg:,妏9⠑'"溮Z 6:aPg| G}(}V G ( +(*ɆT`ȋHւ03Q7GZd@5wan載臼ӷ;HH {/[4; 6i2:18[={<#"W` yک|<9Q}F.v{y9 =#a+=[5|9+ԡʢԑScd=cCʯ٫ϊ^dԺwɜ҇=b} Z=˜G=+KI9sy+maS>%}+}6=gQ y^1_)^~0/Cőu=# ߦ "^~pF)?[=(6;ߪ?Q+yϦ v:ꓟ2T~b2mY ڡ_"> ~(?b)ﹺ ,D&6DSI)J8m0z,8&T>(PH& h4kڤɣ %Z8 4С7ʄѥLkbDsԨ2z@!C;&=6ڵl} 7ܹtڽ7޽| 8`f KႎL40&%RңF=30$oN?a…hb탛PM0ȒmT9˘MkԳVvOҙ@D#P8ۿ?.b9vdQQn'F18gMmjњm(l f ‘!iȱr2UN>m#MZU]u'U'yC; Wg_RNIeV^% P=EU6nff!Ab("p% hn"q 4#u9AwK> 飾⨢IB!Ւ}CV*j "f#܊ %)qa )*| ,",cJ;m"K(m<ʴA. ! +٢+n;(qЫmF|!ʽԛ&ܯ| .C&cI;1ؒ )O0./K + $q o)Lr%C*sԂ<SC$h1uVCu׿J Ö]LF6VJU-lMtMu1{;7yHb$bjja@9ONy\.%(&xhmif5@{n 'E牦g:(sAALgiu;RcMa ^UWYy)dWg]~jr~ӗИ ^:g^CZ-͎{;]wP ĈPx<5y͉BS %5{+ OBosӁ4=h~+YSKV'mv~@LړW$FQ bGJ"Q2g&\\ >h@ ψ4qAM95}_GCШ~ 2bcD!Qx)!w`%/Ljr\rp+b*A"A:X`"`5r>tFHw2~lc!ć!/bHaD>q7 ps,<̔)ǃ==O+ҍ5.x:Ҏz£hp*46hB:| cf#8x(Cj&YHARAI҈~ N)jꄤ(A LO8q;AXԤ*uLmS U  wx̧W 쳅{bivf $jL?Z4lېn1{[U\zًҽo%Ax9G_^U}}`4Dj*d c9rs\ d@X~l]FlJwWqyI^-xl>#82viе;/2紡:F|5C\[vrA\e^ N" 0D^̢jnOmK-q4kZc2;y+{_ z5*mu4A iN Υ5e0aBA1'F7ğ/A:cY-!FctRȈx39ѳƱI 5231K{he+\W [WwrgԷݴƭZ4nlm,ln"P[ S4K?`rAy [vmPEK |Vxk}SLpoJ Ȁa$ H8+E+m\m@;5;llpJ~ 7fT$qdQ(wG|Go5!pЅ`ȅbh]8|\х^ Bdfx4jX[2+ٟ ?Sٳx #:䪘F+jMU T; UiZ;\ @:RIEJq om[sv ДK}U9SZ˽ ,Z @` ͠ 0 0p {;۶] 0` i + ^9< ?` fP {; UI&L[ C%L$1<1< ` w<L7[ _pH 0<&J\ H` 0B,\àe WA$\ ` G }mBD{JMgJՇň ؍͡V]V ٓMٕmٗٙٛ; ْڒ`mڍp}M٪ڗ}g@T ێ@:x$'>͂ H]؄U-݂ =m݌ۻӂ=-ޖcZn _܅= -ߜm]m N-m &-ROyS _ !L%n')+,-1.3N359;7?AN>.EP?-RNyۼh̝ݱ[ _a.Ot(:6ruw票{=i$P~%o^nqމ ^dd>%n8飮DN굡꫎7NW(^n6~^~~X(.T~Hn쬗`m.옘_1:އ¾~^~YU8wr{9aqq'r4JG4"{{<r-:PwJ7u1v- $t*//O//u&57k!2o O<"+/59?4C%E:M=O0c7 828xzz'{g{O{{zQNAp! Y- ,`%P~$Ng>oOvNu_:x?m, Q-P".NBtqd!;F19C~ΚDQl,p(QLnzo_L?oʾo#@OO,X1D@AThNt\h ,Tӱ(Jx$YIhPdK1[YM.i̟AlR2I"T QRPYnWaŎ%[Yiծe[q^*IA I6_Hiq%|@p7\ 9$u` ,TpH Dd(zp 9cd6u{`'^q亁'gsˡO^}tٙGfYѼKjy:U*ݪrկg{~]R˗,ZP*s#$#0ӌ3t: Tc-i f{#@[tqFkFs1{G q1H"4!TG' Ψs *B>,rK.2.rjI 0LJ$BZAHHB:6M3 TȐ`^D3Ղi(H#}QI+4K3RM#}ɕ(J+rUV[uUX 3VBS 1Ph#9A!h؆M!JĕlN\sӭ ]uݡ>mIԎH-u/V~_ᚕZ$3KDlˆ`7 Hˀ0#&*vQ-: *AQzbc؅wdfE^nV7g[JM _*7`viX.8/2]aW3蛀;].WEQ_H#)I_z>)[lģ]j񻖽6l=:T(iTq#L43&i:Qa"JOG=Rxh_xH$!_"L」]P]QWD?U+'~zJ>u#'|/=>}Z={Yw^dwvIDv1)ol;%AOUc`/ s c_XA\6qG\GInw$_T2?Pj8%Pi{`}, YA#qD4`>Q%VU"q0)ZW|X[a -rX"v%1ir"=BG5qtcF=摍Q 2"7񑇜" IJԤ%Grrd$בn!;"ɭ$)C IBo $mml=*$־wN!wei! G.0thP ]&Bq͓TD JݞXp M(\d .M)0Pɗ-':x%oPsGl m,.PBܑl`l@NW^@Ǯf.%㐆ƳrX&a-J#G-<&oG,d\s fġyO b6ߓA BpB̓f۷7eɘ3ФyCdw t4iT 2M՚ YXƵx!X9Zeﴹ.KCt@-p$ɀT\[-o0 nK$Cy s0vf|YeHG7\_3J,M^ 7I#$ѥe"kBij؃ {=0s3CA ;>/3><D#$C[As>";:4P>P>)&c #C?%\BP?[# 6*k':4Ճ*jC7Ps b|A4[Bk -?x !Ø); `DCj3,h ;`@ AԂ$ش-;BAWV3Ƒ5&E^D '\:(<ʀ(4$f) Ef 5$ڛhqxj| )ˉ!GfF FAj?fCt6Ƹ"j*jftK>@TUdVCYś,KE<ɱE||j>SDEشSK$侏$Qt<<ʣTx ɦ|IHIC侞 D[#ԸDJDIaʘqKHJ<%ɫ%Ů촯$TľŴVuաYmBm`8hT@"ԽtIJKM,B(5KTvU#1`,=U|TUmWegUqcS\աU)UHV JacMVWf ghuiVkE lE RoESEW,WrR3v}Y RWSך}X5=[] Uء`XbY>eeY9|֌=؎=mYˊT WWCS٣)$Ym' QYTӧA-5X8U` uXcE֦[mVګ5Z!]ھۄ#Z[LזM۵ j[z5{[R ۀ[ۡۜs\ ֵuu-Dž\noH\T\3ے\\Wѭ^w^uDH-_E70_%_=Ł__X_9;p>_D_4=p;x;8`._D`;`*`6M`e;ЂC_`<``7ȃ`4P0CVaX/a!_&ȃ!>b$F.Hb&-p&!F"`7PC`%^1^-cc^!E_n_9}a``]~< -d;`DcaF,d]uaK&aHb/V b=(bT^eb$ޙ^'fUb+JC..N/c45cc4^5a6& `8V_cj:NaJc&aGV_@v`m>_ qNEfAfHqqNa`aRV@eUUb_&XEV_8h}L߈h^[\^]&^Vb=I^fbެc~iIf]nuchv_ii%cxfncf`qB6qNgIf w_(狎bWEI8趮-LփeD#b!X^ (fiv!bV_h>v>snjdnd>sgd_PgQ&6Uk vb^AA`n*bփ^.ilfʦiljwжo6pdmFsFg voжFd٦g e}nbaNbmށH_V՝k݂փ~&6_b^iHqW" 3`F0^p"DDL,bAgLSpr(#'O81(R =.+/_-o'2=.r+=s2OW@S@Q@:-DH>C *t(ѢF"Mt)ӦNB*'Ϊ!Jw_lx"fqy@_j馇k=$[6aCYo"Vp" 7QqG'+"`Vdqǖwu7!gy5Iף|q(}`I〝w _n0!Ol!Z` aCfQm!m蛇+dq/Ԣ5N@蠅:tPZ)p8ajƦDR(JNHDªM̪EqjLHDO4JHѪj,NAkT\i:jD?0L 1D? j mlolAAE\]0F4>aZGk(Z'rT# 2K *vz볥v *鬵J짻oϖ +Go= M2M1/:!w? ls\>ݎjU{:PhU;@10cb";pU@TZ Zal)vt=iOyhTOwR'<PAgCJ-D)P!8dM(rR>AEFPTA1vғԢNh)HAћƔ&()PA'!8ŨRGQR⢔HaRJ\9m'&@UVvytӜ3[) Ha4޴@Wru\ {NG!̰,`> xc )*6)*|Na,e2 e(0.pE"j[6LaE?MTK)Ū91+'ʣ[Q9:/f-Wpee9Wd21g2d-}kߞfTp?5\TheZr7fK,]T8p m0ѫbro|l/8G=8/[ Qf$ aZ7kIѽtW U0a~qD|G_,' /y0viVcXH&p&84Ii ݐ,4lGS2ӄ%z r /yRT[i|(VKcZ}c~+MT󚏶[ F,ْX0*\)-hK _;6mc#*HYđ᤻lLc;ۋdY[f# 9ӳڬ\(9g]w۠U1V|Jzܥȩ\znu n'{N?ebIs6+΢MC,jqw9;uRn *_9Ynuu%d go2a.:p9H#^*UT/uM ȻErX:;Uvimm;/Yusq Go'xPO<{ߔk7siahޔ~Y/l=ϪaoC?<\{M=W}§ç!Ou_|َn;]MT޵I QХ@E[ݞ _YJ_.JJ!X :]eE`M`ށA dB2UJ5^ ~$^ ƙܠ LU4/aj qJa! 2"]S6mle:1T?g?u> AZ1H-ժDXTU"WJ}Y=TIBO(@d((.bOEIMK 0c3-c3 l,NOe'PQ!7Z#ܔSU1+1^*dU5:PB)pVKQZ`1\]?[գ`@VVceևi:CBJB*B%bpg*CVB$CjklŖ7amV#di! @dO.AePa-$堫IW.KFZzV6 4!23M YL%$$$%@S\Qr!%]RJ _f`f!ƥ`X%m_Va2z}eXW!*]Z&%pߦ̥^ƀ]j^Y֬ JܥVv2QY0 Xbf>f!ӱ"ft&ŏ$jkNllKm&a^%bjg!V'W W] bz&tnRSve虆ax6% ma'UK[9 @@((Z**")j hR餪`jh߅i"6)elX.v }YΪjPiv!%ƠR_@+ڦ*+2+a*|*Bi@݉ªjRkir R(ثk~+_ ʙ'TN(zrlUyln%Z%6,>,sK"bVlѮಈ!XL>e)cl,z &.lϾ*jJ2j  ժ-Zbhj(mP& 1!:Zm෎ j<hSK~T?bbDŮT'"H>z#EJ8b+NTW7UR>O#T!fo;6/-S=#.c[)$F$BVA:AjdZIoVdM9M$,گeq*t6ZTHH.dbjkV#$L.R-fg,FjފJlln"(. m&A @NV.IJFNq\0z*<-asΏ0ޭ:+p0084̣0!;\Y6'^w\$1D-?W!ΰ-19g1C2uT1!۱q_ F+..ζr'rw%F,?$_%&&y'3ɭsq+j0 sV1^1dqlqtqQ0XFm#77-).1;-&&0՞2qo2 r|}1>s6S!n3#AL6m!g0_DO4*qsK PtG%aj xtGHwCJSEÊAL+4M6ti'[rDW5Ԫ/kp^z5X7%Y(ue [\6\5uuu05`a{apbwR.TMKU \k{2F# ;CQwo'# xTegCB34A>(H|(ZOǗ$kz/akz' f GGpmMpzubpsp z9?zf k ʯ!'vk94y?LP&7}c.3zddK `cts [  /s7۷}xmӍ?}}n[nopLLO=8Y$b/2adH͏8;{ uxɗ7kdDm(O;@)j,8x@-{'- 'm'? -.8œH39s4Ds uܑ}J=b6z" h  `AB X@t -S" @ j B'4b¿(! > K-ܐ 7`!; ŋJ,L#`ѥ2nl 5ݔN=*H J޳->Bx 'It#R:PС˅t@   T ^+ZoW:Tρ2!"Q-wr 쿅tcƎԧL?ݗ~1Q/ͽ5ՁbV[ql !@0A]YMK^ow=LEt]vݝ.R'0ߤ^mR:u!Rpدj/ Bt  kpYg 6V֯l_V iY{w%N--/i" 6-B Z\:XV6nFyZL$oq?Nup ތ\˵ߞ3'HT"BzXo5]V}w쁢PuI kw-l-p O.Bn,"CNq7BZ8Aff I. q)HъWb|*n Sp7ap < (^,qCA(*pt &BR!U(S8rz4 wLB #8= xtPJN R<'1AWbr -00})9# yLf3͌vBLaZSŬHD1e t5C-Tf`E#8*"DP0 e0hX@?XP*ߋZl % &`k|A ` C`# "20qҰG_ETPzi e( (!I:%x;+zU#mYVP T;CQ/, Jб;UCP ;P RyiA Ҋ=ՁPH6<d)[Y^Bf9YfAAW4|}\Uj=Պ b=ՄSۿ϶ nu[Jp[\Ur{>}bjAJ9hJ-k]ʹ"5 B[ߞ-Ez ^׽q|[_~_p;6ZI` [o{amE<Oֳ%`S„"~!9NuAN \JPN@좶0s$cqe/a\f3grf7^^s\g;ywg>͡cu ʉVx,W8%0%JW:7t`4iҦ8Q+>Z]jS ]H=Vگ}jY75Ok__%՛ki b NmlOZź.]mk/%at{=n#qv2nLC;҆2nw%۱ݶ=z[޺1woI79 Lr= u8'n9V7 C-|AQ* ICb>|$MWbҔ:,>ƠrI7:#)IW]8gϕu_룼չuwe~v}Qwڷqߝa vn GP(iO|ꓟ' 1 PSf8oT۠Mz6y J(0^ ni׸ Qy/_|}|?ˇ_K~}gש_=^'˞ڵWwoO/nY?fR,ȏ"Pnn"o<,104M8!G )M$Wc*wk /Sj;a0meo"r ~p !LشÔpgC 0 Ac=p0 p ݰܨ Pp/'`~@- 1q/ ޒ0 L/qi& &+/+ 2;?CQ=qGOSKQ[_aQgYoG p( FFȐȐQqw~%c1ȲQ{1ǾGDZPŰSQQ9Bq2r1ʑ Q Rѱ RG\HhR'h /ȩiR$" "L$KNT%Cr$%?%'&A2%[2'32&o&ir'mR'u'yR)R'_')()i)R+*o+RIJ&}B#)) ("rG` Q!,!2s  Wo!y/3/PHf"R%013u 17s%:314Id3s!S5ëq4 '6q64kq"@1Cgo55\5aS7839r9397}5S8s4r^13 s0;3:3\S=K#4 63>s>>u>q/s=:%ST(Q6 t646t44405TB*B/Nf:C?DCTD? FDOESTEWEQD[FcTFg4E_FoGsEuG{TGmGTHUEL@C5CtBBB5CHG"TK4E4KLgJEKLatLTMo4MMѴC4IOJ1I)B D%a}<4PE4vBC3&uMcEuP#aQ4SAQ5R'*R@S?5TuDKR;TR)RWE۔W5D}XuTO+O)5CK˂@5ؠZ%a" "[ UZu ˲u[[5\ǵ\\u]]]u $a_ [\ŕ\ì_@]]b3v`5cU^{XYdYRVeWe[eWRv"@ew`J rVg[vVe'eSvh_hi6ic6e#@lVeuvgYg6kVkkafW`nifgYhK@hkm]iVnn6nooYGd5C pM`n ewlKq7eU Pj0Vo?Wewl# Vv"Xr-m=72Wmq`s;tkWtQrquu'r/7v5s=vVqyyyVtzpINp @ʀ@.T * ̀} }Q`}}}ӷpp7#wfVpp_}W}~ )x뷂1~ i t#W  p/؁]8 3鷆!68#p#` UpYXmx~q؂xqx88؉g8MzX؋8O|S)|U@Ȅ ./{Upz ِyN7I@Ny4r zq4Th p;ٓ?9Iz#YC'-o@99_AYMٔO YO wcٗz٘Y{YyOYCOrU@CYty yoOXnWYCYCՙyWכyzǹ9׹#%%Zd1zfVjou6 6Tv9+gYgyIlOzSVzga:WrsVIr5sTyqুڪZCy9:5dzcړ}8'::甭ZZ뚮뺭zBڭպ:[#B/3{![;۳/?[G[pCO-;WM;Y%oYIe?۵ksK{[};a;;@۹[%gۺU.{[ۼӸC߻ۻ图[;=כ[#|#|($BKLJTSV[SesP[:+3#I9M1(6021212122./26:ND^GiLwRĔH>, }{xwvvqd`}kiouux}|ynx7HA9!ٍ/yvGY:<FXGB CIɓ(S\ɲ˗0cʜI͛8sɳ'ȃ@  H&CzlcǏ#FfIݱU`ÊKٳhӪ]듨[(ɝKݻx˷߿ LÈ+^̸ǐ#Eɻ2k̹ϠCMӨS^ͺװc˞M۷|$]ӂ Nȓ+_μУKNسkνqK5 ^ث˟OϿ(h& 6`{y"\|tX& ($h(,0(4h8!8H2hL6pH:x̣> m1`zRb@9|-lߛW! ̤&7Nz (GIRL*WV򕰌,gIZP9Pob4=|mN"";ĥ4IjZ̦6nz9D*V"t0h~rĆ+V҃3'-IyÓBІ:_RNUdd1ӁA* ~.ӓdBWRP|i*qT4)5(B!Ӟ@&82az"ӥNGGP \8i'[ɛiLazJڴ9%&Զp *81A&#. 0{r~z ?(lvTЃp lfsEĆ<yd?D cf2m7ytgW;Zl4-+nY[vs-&qPLkAVqͮvZ -QGsUM-#*XbAՁ f``Ѐ$ PBnq[fͤX}ipJafp3*w.hMG\Jz|P)^puW Sv8αs'Xhkϩ* hoD bMx`@2뀺 GPؔ"Τn}Ӯ2s-[E)]ezFtM*=rKmyFdGɃ@ *à>&@@>2yHmzK{^ؼ@|ε}mVa:Y-sl7m^oչNvm'7]%Q&H U/wRd:^/]`}߬OUհv<qD=g;NM̓Mc<5)n']9N[Xzwx4x n' G \ C CU/[X?h?:@e4 P#o"S9Cd bP@UmC:es1t pW+W&f a[ЏOd8^uFW .! ?jwGɯC wj x&y I@.Wq2QLD0ַYg| "ا}}ߧf7<EDdxWzgLN2^I e!0rprJrG1@7W'QF:P:PΠEg#؆snr8t%hۇu|8~.X~8OP&^Pp{uC=xrѤIjvJxk6@W? <0?/0(z10dI QPXOZ J L[J0ǠJ eE  0jP`tڅжn;`ICTLPK;*{:z8[<˪j2`51hI*˹K;۹t W[tW[J@кֺ䴛Դ&fJ`bKkd+f[0K`O +t\zګMyK} @;[%kxo9i6=۪O@+)˹(d[{J庭{ |ԴgJe`` ˮKҰK5#.Z[Þ"Դ @ѽ! #{H I"JIO ŜQIU,JWI[(+U0jF(;@žn|p,Ǣd݀ l!m+\ ÛTS Jg+¨ȸxI"')+ -, `50 {YIJ< "?""A#PLlˀSlŘʠdR9[r!ѿI{ΟɐJz| `J|ƜTS @АϋI@e{Jg{!ekI I. D+uIIW  #<첻˃ ",)kӅĻTҪ˼B6͡J\Lܱ 5zP Q଎j>%C%$b9#bM'Qp"`-aM֎`,1׊ *B.CS"x %%؀ "+uRX]"v֌=ه"٘(1R"^""/TXr(#W&#ښ ۯ2"-$rֶ%2l֊ Յێ}t2Ͻ-=؟M"۴=۷۶=ݬ=޼}M}ޫ۬ G-׼"ՅVX=3A"9rf/ `b 1 c|//D3}@dRERN0S#3z0=z`S5#5z0S6x0D9x/./*uop{.twn.I/sZ僠V>`>NV^E͉{i^AP^-3/~>3^എ^.cyg^Nr. Rs.0Ԏv 5:.0~25E^F2O~N0\>Y蚞/.ei2q.zv{~ mn.鋞NFMLEx->g~/~潞6ӞʬC^.H/"!,/V_N2y f08~w0wܾ4\/dDKK&22j>^/dN#/_(+O-o!042N3:?yЎQ/=iZNijJ0!._˾0?Ch3n_q/iR-%/H =  >>tb -RhQƌ+~QD|*\r,W"t2fL ]T͚2u|TeϙpC` 2h<<6SHߋH+ɽ@#t2:B-¤jꩨĪqi^)E,BK-pK%$}Dl,1:*3PnjCUI&L,Icr3!(IRwq5yMތ3J+Ԑ Uz톾V醻d31G?}g_}vr3|CiYݎS@ёN%\6Ё ] Uz`5Ƚ $6<' [Q=o(c(?ue Cpc(?8D"шGDb!vb8E*VъWbE.vuPhьg, ոF6^_6:ֱqr(^nt>"9HB DC"HF6ґd$%9IJVҒl̀INvҒed#̀6ҔDȴ8)](0'9za 0f0I ҘT̡}0z| %39Mjmr_ZW N-GqE4`J$K L&5B5L}13OTT6_89fDV=AF3 0tԤ'M `J/i:Ԧ'W%0*  |4A@>}OjT:UUA7><@}n  eMU+lխok\:Wֵrpth,Jq>LY{H%B`L!Db&TD eaPVa5gcZ5B5hXa @bG 35l 4_\y6%Dž~3$w؄26%4"z;Z!J6t?K(qde2^6]Ag7@^rV@m4ɻZ-@ eZlzw+ow2lq;P{Pqn$jZ^nWtxU A1 =Ϯ[ӭn?Ös9wy\eT0‘w"+ q Tz^<tTkɌT*Ӊ3yN3B̀4Q ,><΁ŭĀ 3и b@b(taԀA y oNl͐t3MOlv ~t۠8JXkPfOղA-;P=;1BսⲹJ³݄h8g#֏ ohd!HFrɍ,ԛַMe& lWic_ Ⱦ+n$P\ \3 8..ꣿ zs=04lX8?Wp\8&{շ2;;W  6T0705b)ܒ-1H@;-@a kR%sK<în.]1ț3"S{,vt0/{7|z7-,Aث=%Ӣj=c)-`$K3>KX(=>4d)3H(< tS!c),#@CBĭ-Z1@ i6Dhɶ*,۱ɋX{#Av輙E -8z2lR"Z3'd /(Â5B#B8- nC1C,93T.>h  ::Dh303ۙpDD "@⃇4@ DIK䊳D tNĒ,Pt#tRTDEC'@jE#2+t"!&뼝켠-<&ۂda$ԋ=kH /rƌBJ@8 ۀ.(GsK2(͢KK9G,7LC1Hl@ ;:: D|@:@>x̰D-u;.9(* M&4|-@63En{osuA2cx@9z#`)(ԁ<[1ⴁh8{-`1 Cd)2P?\-33LH(?T4<QĮ0 ̣P9 d<9b;H _ ̑@jbM6*ADS<,ڔۄ,X6)d}츉8 LʼnQ֣6Q̨ϮVPZ c [2WQTHVVq%eG}۴]p[[KZ۷#R 3 ϗu%SST|ȕ-"M\!҄!\$\]Uե5%]=]ҝ]%5]ѕ\U]ߵ^{\^=]M^ٽM>()Y]I]<$X+*(x'%@#9`u$#J]-^ME]`"U`_]F5`m}E`E" aЃ1h (#0$H%&(U]W$^|] %` \)`%&F".]f^#^*+.ʕb-Nb/6"7&c(=c Va]ca]ET2m\lAeDW8&"F(]c4cHbIc%vV"Lf]>VdMnb'cO6aW&e:.QL1[YUM_J_VMNd`Q`=.n9cfaf]]LN]beecJf`V.be.kcLbg>6\@FUme3^ApgAz-f_|%^^wõ^1eB>SC>7VhhUh[dyvIx5b{אffv闖T陦e?0Y\>]~\4i&꣎\FjBi釞ggD^DV#fjȺF^i"vܘjaK`yBɕ빶덾^"N x6Q^tO` .lpdžȎl*l+˖φ kFASfv׆ؖ٦%m5 b^nV91n΂;xXyv~vfmBa>6mF&vچ~mm>nVnƁ$HWhpP)%hpZ phnkWq^mƂop~P)$x!HM! nf2Xtrckr&q6q&Bi13G4W5 >!b89:;0nhonw|w~En'_n&tʁ-SR'1h??0h~?'/Ggw烘:'yGyy'yOyyiv=8B8?8)qqFnnyywXF=xn{&&'<xwTwhho{g|^|_%7?꫐ņs&ṁ[OF>hMPoצJЄLJomZVЄY0}ׄZ}?Yhgxm5 znWtwq|u$`o#ontzT_ r`0?"a 02l!Ĉ N`"ƌ3Æ#ȐpaC+nl%̘2gҬiL SӸ>Z'RU$ҦOj%OX5E\4sϼjRC&3X԰mF00` xU#9+M^u=Sa1dž*>$aF6లWqdy Ucx2܋q+{†7 O΄^'2o\ ˋ0>}cGD# r YN+obo=ѢB֢瓖I$fr)WfCi,zcKg?9W'[%5K;j/4E W"EW^a\օ#'zꭔ!mW,ĭ:꭬>VdDܸ%7O)+e)3!K:-w Q++ayAW^E}4ۓ ~T;`nRZd-j"iR`S/U^際"_:ˆ) -$+mƶ֊+I #\BNyr68*'l]sQv9O[֖Qz䩷An:/)8'Օ-0JO=NzVk pjw+LX` XdA~ֳG=1NvØD0;ެ6O4t]ya?zBathByZu&rz4֔bPȣT ޾/ m  T64t=谇!u qA_D':1!w4IgsHG$3(-q"N&rq#5dK2PanH*zDi@HȾ&2kGE+RsВȳ. SgQ2'dSUH#:ڱ/5#?R ! "_I!f+#.LC)IFY-VsO`#.s9D^2g/YLsޢ3)};[ ΅2'D9#*Ѳl,<;X n@K0ёt)Ѐv=\w4Rd*6KE4:1Q$Fm $p;D5X)4Arud)1 b +ZVnUk=+ZV5c-+\׹v5&mLRIg;:F2:Ez*Y4-Pc>5HjS=聪U}UWRa؀kXжՁms*6kJ#}.t+Rֽnuw65SU_ZӉdAPIAp :fytVm\,=6чj|@ ~6WEѐ~u-ok )D dshKkԄrQ ڡ/ @Xy-Ae, <'بjW=%v4h mm4Mۭ{AqS導ΏB,b9LxT'28*np#`4a?;ȯ4lMh\ nJ6?`Nu~ (NӹW6%\ :(ǃyGKa-%c`/Mm!l[}_\V2 20@|]\m}dYt`p< {/<# ~ O?+EJΣC1%BڬCHMMU<R б^;A= h!hVUA]\ LCq 4i]0U )Ԡpl44@<8`g]Xi ,6@0_ߵ@Ձ@$NQ< a [G< -CaZR^R[*n@@Q5S9T]}UY[,0  V͂(W@tA (A"\=V= <)@2B X?`4ZcBfv!8!͑?!8D:~:H] y3GSH'4X#>@C!:!"<("##f  0U0%:pb zpXՕ[\ ڕ05l$LnAp0]#)` ؀_dOX" _aCh#2@(DVMʡ\IdW}%YW6BcZDَX - @8'J%$($C6#"܀DR$*ޞ, 0xdY J@ @ X `+bb4,ʗMMz@X"24 ? HC8%7B%TUFbلsex4]D9FcAl@19Y>UBc;X aAxͭ-DS4&,"aBd|$fQ-f9EfG~eL @8֙kVf\U!djh@AklB!p&cN@OD_p @4m#TZt.tDGLqe`hP'!5>)FBDBtwn[d%AQRfDE&`'r߁e.0E*€"hhb[bƱ3"'֤N6d hLAY `#Ag ULa 7rZ"ƪ*֪@Xf`򔗪 Y!f!RQSEUFyX)) lv^`[U&ǹU" " +FB,eSp+ DrrzKV ꬪE\V^,VlKwAw[N԰)aǛugiFkI&6Ngf)vp,BAm&X"0,jOVY-֒՚6 Co@ 76TDNv.,)v,%)v.Plԁ8iB'ɖlD؄i:iE1Y^!_\) !V_בs֕\s5eN5T߲2P*rBņlšJ&ҩl.(GnnQq.YE[plűns1ҷm̮\&ݮoMb{8At:NCoKK\PϱV/d^VI\i/qok2᳡/s:F3NMj֯bQGyQ D3* kD" ߪ;Fbn).[Ł0)l(oD0;p<AnMȰAѰ'yN찮q6F#F<2$*Pq?CV0_0Y/o0r@+2,r2-ײ- X_]Vw2m/ s++m20#2?n240s3;s3g3@u"SrFX%*8c9sf2l&肣 `=m AF"a0F]aAY*q0DCo-PQE/YF_FkG3Y]t}4QYCC4JCI/YH4LL4M߲Z'#:3:E3Pɛ%>^P,u\Ez`Qf)V iUZ[7[SsS`u3MR _R\.(=.o8'nbB1g;)kv~~x[~'XuAJ\5RdY䥘z$V%&Ý"]u+UXqUr$,["ˬȱ an5.2-rτNe3h7}7"5@ye,H ? >,lm#R?^%^Vde֕JJd⊏pӵ(V7_޺1yT)78_|yf,27 K*E;qEpQc^!X88dD&V$ 4Uȁkd"(r&^5j~fu8uuKܶ ybSq4v3} 5?P7~Oy؉KBt!x.ۃbAAX@߹̲::7x-[h&(:z[/zỲKy7?:?5;d4sc9^So\Sl:S#usQT9d_{{/bic)K;\Ǹʵ^=:)*:8{ďq61Ķ@r{9Ks%AkB 7?H4LynE%W][WH+8 (dSDpA{Cf?W"h-YG+(<84+d{敌0;̃9μ_e yCKđs /Ax n/|+qU\{=r__T {:V "}ݧų=[Wsg}`iAkrn+ zJq">*>%=~WYOң'8yƲe5c~>҃7sDR0@0`AnHaCx3D,rHi"#)1x:QH'7DJŔ9?1ՔSgO?:hQ<U!L&jՠHe B&_;lY"^ѦUm[I|UCƭ,j[C`B:Ċ/fHQ>lRrțC/cjgϟr 4Mg5QZD lgֽ[\t;\ _\0bON)}ee$W^٧o|xͧO:" ?vBwٚ8 L"j΢:S‘(H<@nC Q=Xl6`D  +l9NB2B +äRl,( &B- E۲:bqFjdK8s4 #H"t,I*@C7r2!]̴\kMtaB@ @#ai`uiWV^7uV_cftMVemCDUiksPo-sM7DiQMhst,"N Tӄsh0t9*JYiY6׉ )&XamII;I2g9vٗmm-jYggq硉NZi" ObM^sZAKhʜJպ_V! Na,|u ycUս׿+vev HhqWYGϜσIGn AL}2iz*:'׺:kng+8 UCf 5|3&dW6K@\('r)( 8GɕBc?ԅuYYlR 1m_ 'Q Mh;q-+h/KkydkC6zsž$O|Ct_>1o+\'vՏ_L7P4ȳdn ]3 N1q29zERj&fZфX[&>Q] &S4<(o8#ҶT>GMdN/0L#AGhOb gii[xpޑ-´x,L'>6@zJꞔg~S5A@聭o{'9BAU(\eWSQ|z+]]qHERFͤNBA 4l3= )n:%4{@ 3Po.qLjZ>,FN޹p׾:ɰh?Sr$:##ѝnz"6~ebׂՠ图$58@%M[ M}{d.,y?etg7lBs04o+><*hIKZӢvvu?Z{=5ZTyCЪT}M;t6W$Ro=W d/ճPa$x@{ͭ2Ylms cG;ڳ }纜/2l]o ox]-_xk>V\)|q_Xt#OPMN.4g̦euv^ 7/W~@5Lշ>}8dQ0dt(yBܱȷf?wg  pTIo/$P,0)0/

fA'k(r)r77285+!r(zp,,cB |2 #x ,J}\,Fm/;>s<}=uH C>?rtN,G3>ӣ4@3kr(aK5Le3BrBqB-777TC;l2i,2XNTӈr&Jjt*;K{G슨5oΖIJQã3eDnKIRaKc5'+8 WB)dhPO*; u.uXa;k0QG]tLQY[Zu[[tf&'a\UʵKA(vn2(TAAS'W'[M1NcU8B0j!` 3W5F-; b a UpuQ}04"~;kPBq<~} Ћgɓ nqrt@Yd CyƤ:{\sӽ[D<'e" @;%?u*('[& }[Љؑ=]NѓG_ !`HٶO9|QazAܕ˝G\]ϽՃe=!h=ά l'`\\(.}Ѝ}9aٙќ `sҙbNrane l'_vv[Y^w]ť4b>k~^( =C}\h@~빾>~ɾ^w ~~wbb`q෎'4Ԉ O^eYyyCiܼKb(v47sׁ"> X]a?eimq-ٸ` @Y f m ߌ v@4͠q} _p{Y%'(&, 0_Ηש ;0a p +Z1ƍ;z2ȑ# (PʕϞe˗/?I=MZѬi秠?5P&68Q2Q5&֭5 4 6رM!=:}V˝[nٽ{L|ޭ7`[8&d+__94P?)3o9g դ&B#;$;ݼ{L2aFS-yC}%暚۬IKZ B+:խt ~f׶V.]zM`؀|9@VLeig)ive@@Ө֡B)ч1[.c%O1%y3DTtKt׭d];.1Վs/zi^|^@b)& FjhBJr"sމpgqgs'.\AR&a>JF0(X2niHip/ :t7MW@48s?,(\NeQZnW_ʆi&ky)(V흆&(Bn;iJkH[~Jofjq-g$r?QMTέ^WJEUX[,. 31m}͜#n6/C(ҪtOWN{rK<4dvEӑ*vc8-Y B[]7ew mn 3A{?2 >('>A) A> I 'bApǤnu lWؙvn jp~2z#;x&|Yg/N{x 9L80C̍}6HрoNN@@pi좣0qd,͘Xpl;8Q%C=zKaߞ@Ppo>* 6H =eNOސK"I$a\BY3(K?Dˋe&6r  ̪ԑ&!G{{dXH%w҄(FS@G8F0r u*a `#O ́\ K-*,AӅr / Q%dOe5c ߚB+9a ,ğbp& xpR@$$NI [|a<*'7E6peWU2bYϊִulm[ZU t]׼u|_ v+Z嚅*vmc J6jDWRQUjP6 4DADghmĀA /A 8m:+pӡA9dT+U4~k-V_UnD FŻEyϋwm{ w}ݕw]/`,g,2Y1✨'IXAx NirjB4wB)8EuœT;f4GKAoY\G>tRNDM@IԄ (o(W)b4)ha(cHehgicmnnX:cHbXg9P{fb|H;@EtC!Ј0<2X~T5~G(BЃ?7>DLtsUC FpHPX:ÊX98@J `:Ћ 0(a f{c)7 w:k7٨񍯔 瘎QHV8U (hb iXg%W yWe@W^Z@[^@yWa`vّtU\P>P 07H6hڳBh8h3Pr54NHNF`CH*&SIP?B>;8`:l80 eF;p\ Kgi FQ!u!s9K8I PSJH]}@ } w!i PN< H[yVКMǕ 8Giljɩɜĩ Љٜizp0x`ƙיyx?pgM1p/R8CDCI8`2O?bfs(DR(ZJK  J l)ae129@4P3FIKڙ9@TI  I@ MћJ0za Jujwy\z w*[J0F,Xʨ|& )XmDQdVA`~'&&7 wP=98&<w': $Ibrw]^Y#:ƊgrRQ=ꌢ ~9!0" JpՊ 1AJ*]E陻!XjP Ol z[[Jk[QI#*wGv!LZ:PZ'ZZ< I O@\BˊEuI2{󋫘<Ѝxfx<lYT{9M(*3RBt4@9պ7ĸcJx Piz ) pM@1 +!K%&?Qⶲd:r}d^w'C ;9Iyb%xrPeHȄlQ0vc *K DF(c1ZMzn[!ۊ)v.AvPa;KPz{:YNli: kyJl_Ѻ';h8YrS攤3'v?c|)epcQ:Ǫ*Q KP0Ylptad Зo+A.٘ ŷ` LjZ qK<K ɢ+$"'(FQ۲+F_r|î T=@}b&CI|MlJ:&zκͨ. .j 4O aa ˙~`B7x\a{O 朹x[ˊbd{|[<mmЁ hMB=]i6|q3G PT='Oj'Oöh*'TKv(@L-c'! :Kdw(Pv~KQ܋êIdJ ;ͥΫK@λ}ݖLmwy|M~MdY\%Gi(givA7\gc\(^(# ޑ ʡ1Ֆʨz73C'DK(Ҷ3#P>1΀Sc1} >& F&0Ld~ 7O"erڂHR(V{Q2.3K5n㠛:~<>-<.&'LCC#:YN4d~c.Tm.rwl^np6 a~,}nTC v@PDq2U@TPM&a,B]R Ac 컽:] .\ 6VRU"R`/@n1uިQW,;ߎ~&`>Sv$_*oD@A'tP`2&bpV`!cpU( eP$@V`g0Ү.SpgҮM`M"Z/BM`G> z *7w~LMλZ> _ 񽮪C Zd`f@ ^Zp{tP, #QP[/O#/S@pP]4 U@k 4@["9 j%[`K3 TVO^VdQ=@zA3hoifkZ.@Xr?{~/]T)%Vt$DStp$RU $XAUJByqKEF!E$ rР fdKBY͚UO|Ф͘IUCfR3X~(ӪY&xPƃSo![ & 0lyM'n[+V" † _RxµEMA>+Y؆\б WeJʴΔtWG['NV la K.dp IB%,['IMK4+ 7,AVgiK"uԦ>]۫mW"kPS U(mGA!{p_2Y$}e)/5&;$a3#*&fC؝T=grӨ sXؚC-tqu#qr%yIAͭ{MtM/ɕA=6 )w|%, AY:e2wɋ]t=.L-ܙS22)ah fhh. t[@n\悜U㩎\4CK0td=_7f+$ K(|fL6͞]Cq% 爭9ݤG;]ק%%K-SVדq:%įÒ*ȚC^|&Z=u>\7l=$P> =` I!t$H/ ȫ!o7g/O+zo/o{F շCr}eB{6= 8@L@TZ&z@|((3W@ @ |@(03x oGT;x;3>GhBK> (B0$X&),1(4P><?:ȠC=F!Y3+.h%)DB,DC4DDX@G;ߡ#,@=hhA9sCA32P !|7*#LX&|B)|E+B-B/C1$C<0C4d 5:ϣ?уӂcՋ3;Aa' CoLDYĈiDF{1%NGp|` 8SL !$B*E%dˆpBE+-/02Ѓ"A=U%dS>0>H>"!ƒ@(@@UXKPE=xA($P-))*$XԴ Ix BBU90.01H 7@`ڮ 2,aFڷbIɁx)S(@yWWZ^59e)F1MMXǝXLMXN5MmXUzXW#U̓>TV=٘h] ZA]uωUbU%)()4@P$ӓ%\9`21,'XW 5[͍ X@e/Xw-\={;i SU)pEݣm00n\X5\tX=%Uɲ?<͂ݴY Y9AU\E%XR=e(U ^(/Ă*a!ZYmaEbk$|^azڀ1aadՂ--+ %.Z1qb(F^]bℰ.1cB,c8ߐؔRxt` _*=M|& H2`g~>R3,䱴uhV, ETeU݂S!:aVneRBb8s儘R^E_1i.$^WUef)~*mlmcof>g5 D9uf1h6z - c kz&{N粞FV~N`88܆n dϽ Sd% e*L-Xi܀%.Yp`p[ Ye-5id~^X}n)f+.B,b=jX%׻q gp 8EyjcQ0qR J=Ơ%g>4M&ѲNdDsfFh빮kVNWh h e ΃†V^lu@h%|l^mN20fUЎ֞e6m+f֎Nئܸm#t~|-02Na)0hܰ2/5 NXA.n&7k&Rkfhfn%~oNQ-<6RmTiЃ?(iWOBk><'֙vp48f Of /b Wm7֞/26q@ a`,X8 9"| rZ(rECfr}N'gkv*?o,gXVUMK@ՌoR4?lF&Yms*DQsȖl<t2t6EtgVFG/|؞tgKLdM[@iqR7nS?+Cb.EZ5 w}zPÕ^Gvrh_rvTK^-/vM>h0 %gU%j7v0;H;Ǯsnȃ.HUe_H34yGmf}?}7HJ_jD 5f!+(ou bwـ@t$u=+kbv͍蚷QJf?UUX<؂ >pdzw&etNpc8qǃ2߂._ /j|W{?g3ܦJ|Z1҄NT@x!aT| XOy$p@7=&P B$gŊ*bx1c>y>>]pGݼ2;6j_Bw/hP@rM B#&Ql#38(#)уG4eƈeʓ%6|D}JqL!D `d0uC~˗%_"G%lsc\б)膠C & 3_YhXЩ2lVЙ7qSБ7rAM 5ScϮ};vaX.~|RUB^NFTCGٙDRempLbTSTēOD1 G R%mSS!aVb cQ Zm\u^}FXc>vXb7aH}ZmTVNEP r,"YfZl\tمW^;2dDv&bl6v$IbXvjZ۹)\%+ؔaGu' hzSwB =hMiEs!;3acdL!6VMaE\t1Fh";c62-IۘC&MԷOF)cS"U5 TefOky c]e`sذͶ0QqFm]w] fLja~Bz0޸㏣1Fckc1P3$S4)E/ɏM* Kعb\9¨.#QfAavlL`" 1 Uf5!" 3H42%#)CTr2vCso]ahJ!R3F$Ч&:}_a33)/n41˙tD##]vow3.<] /%Tz1!nK?4PIƬ]S+YJU%m^s)fƈY' LF׹1vˠ8xM}PgπgB8Z9v{z8^D5Rܳ '>V&ѕLd?) Aљ1:Ank_ƨTPqG)eZ UB&4a J@|0@D Ai cJ0h VPa2(ܙy`򕢱۱0ahV;UP/SdP֣/;6O Ol#0A` kҨw @"w hs!\|x0F0%̭QZњN=O*)+mZKҤ3ڗH@IjQ䳕KeQiIrM*W 7t <`*Ջ,mF)pg#ϼP-/꛴+]LP 3Vg.؟4꒐:P2%at*J*C^fbTpfGx^w,6c _z6^ϥ[>qق@24du} KvpRaLVv(+,QDsżQ9ZT1Ik60fa_$4:ޝkglcܤ:kjc,* %+c\ e=sGnŪqP更9ݪux\\Euݴa+~z?:;Ю)p:o4YA4ks%m/j(^nl{].,?էd} J1j|mġH(ql}M!?ȉS8) Tr<98mx VX\ /5jXίτߙLz5ٲޛg:Qs~hvNRxW鱖DOMM̊?ޭ)_zޛ|$.bG袂pEL҇yᨿG9joIOuJxWo .-|x-~g4w 3elS@ `"`*`V@8B`JR@VaP$Xåaޑڷ!}9 !~)Z`Q!ǯq)X?UA (2a:BaJ2aVbaQݦ͝ uѭōƕ_ 9I(Sߺ__ b"*"2b#:#Bb$"b@A%bb&j&rb'jpa_inMܩ! K`!  `! a 2AR2*22c3zI8c46c(Yۇ|aA\  !ӰI X |l#;XT8$+ZJgh0@Ag^L|6…hΨ !$Aw%i 鏺耒J{*2i{r4iJ锲ĊP镲(rA$X)@zyiy'hʜ)zg}gNh,(^hh:*.+ph~h舖h.hBh&荚(i&iF*j |i˜hyigz2g7zj|jj~"VjjB+x}J2hzh'h|jjjʫ ꀪ~:l~*B Bi6J,iVlb,通,+0k>F'2@^~ꑀ⫕+k|īkkk l|jĢk kv|6*RʁjlxlRi hZɦliةi~r|z+l7@.jmjj*g" nBnv>+D-f+F.^֮+׮+2(ڦw*<ɢڲ-۶DgD{R/{2-n|-KP/p6B-k*oB?n''ﮪo^& /l~mjjJ0Zo6Anov/n𕌯..@lo..*.l_r+60FN(KmRm_m2 ( ' q *p*N c,ǭVm'D&#[I+G12k~qާ[ZU!.bB 5A :gd\[h*u7DHS82\<@9'k@)uuCB(gA,?2 9 y1^o_KJo)7|'6/to-oOvLsgtx'@<X z8쯔AXhA0.@X4A,HA(|*d&~BN5(φ+d ,*om ::~wpӻm.F~:9t|>BË:Aſ(B#+0@~**X0p™<|AԖ@YB=ۧ+*謁3o;:ۧp/{:Rs'xW|;Hb+4@DǃȿB1<˯˗=g~<< =ӆ3ӿ(-7˧ֿ8;kq<ػ4=هz@7sWjޓ[[ |T\{[p=}=N75|s>z;G/鷧׷,τd/~ށ~D4xAH^-L𠦇 RbF4.ITPԫwT)PbB)_T5K1Oc4SUC_d9SVz*$XvzUWV({ %_)QjjCA^ A &_ysp=Ufr!x`GA&H:|hrĉ/~#H kdˤ͜; T( n&x4ҡNJEU|ȗjW|ײ[U-\3 E^?b1L#5KB#hPc+5kҬ6$$t7 ڐ>,H9^$蹠$;J. rL=(*쫿 P@z "̌ xTÁxP;(FVc9( Gf  hϚ 885(xKѢԘM(@7nil(dGb#RUX ҍW[*ɶbRRL0+ r?-220!=8 =m8 C kT;!C ?g(9(XPX}TWL *E O$YdRK} >ؑ~^K[!jR'W`E UN9 8"yf/%(uޙ}k**袍>i.(馝~zq:0j묵ޚ뮽찷Z>}x1z&vjk(OO[{eYW'5ߜ=3'M?E_]<\Xw݀XŌ 5fy_(>S西w^%/)2*w_域ߟ*? @6`ĭ{~5>}dM(R:1 iXC9yCAXD#ITD'.l  #ԢA/ac 8`ٸF+qW8G;1G?8HCc!HG>ҏ$)HIVdE550pQc7J1i%:.K^20!)Lb3D2Nfql%)MJW,Ѩhr2Y>Ӝ:ٹvs脧&9GOFs,yl^&yCsDhBPӡeD%Hgg5˨2ޜ%8(@RLi;QRJԥŌ./{jtfm.q{\9:ux(>4 zK^]vk7ݝw;ro` 0~`$evs2r mOH;TžTR!`qc A9&:5H#Xڎ}X'3OR`ugG4q-VKʭt*Tg.lZ4C}Ї$=Mӕ,5fmn=5'RUգ>s;>A CcP ] ׻h}(v9e-7 (Fq 9q!I^r)WYr1I^O>2>]nmD;< [;nt>p4`x">;+iWG8q^w}pw] lqQ<=ՂѤ\Q2hag :R{YDZyeJ{>?du'U}|9y@ M;J6u)bN`8}~D7sd@ VOgJ`6 V猯d--.hKv`*)+A *ϮOꃈ ,o04nVPoZ( \`dnprwPzPC P(n0!J!J pfAafpH1*8!p-0p~ty֎O3Awu ]*WX0<Xfa1lM11BJ&Q/OC͑ FQЎQqTX1t ^02ސqH!H#H1`f#9p%#%-)i26#ˑ'U2$1 #/nQ&!ҭҝp<"P''IA#(%HG2S%aR%gR1++A-ǒ/ui((( OKkT\ dl : $ +2L4T >l4AF4hJL5I6QL36=5e3[37;l5;rRl2Qµx̵9:s:i99::3:;<{;s;=dz;<=<} x:+fn,X< C~) ̎v`"-Ҩΐ/$ )C `NB=!9-D  D@B ` B4ޠ! B@B F  $TH $thS`(2 NK ď r`TM9AM۴M+MNδMTMtMTOTδaaq+ݔBO7M!URR/M@s s)S rtA Tv`Cy@NVguCvq!8t5  vqLT`/" v1\\ԑv@9F0` v/\ԑĠt t @ @v @ UJ@`Q`w^a`L( . 0c1VcAcEc`.Nb7veQVe@eSbaceb bng;6cqh5eYh[hVi458U< 3 l Tp Utv@Uw nՏ>mU@("XM4 m9!Z))[hpj[m^\]H] 4׏Iޮ   v6 V f @`@ |W ࠍ `V6@y7y 7{}w`` { wKHwwi9j UjmjkiIk Bmll1\Om0MvW,o6ooUE (p'p w 5qʕ @'Ѐr׵rssXWa_ tt8Awvq`wwwx7zW}cw|؋~7(A1lT Mk6ր!MBw.D.8>t$*5DmYTB#MZCF20#G @'tbH9A]ro ҀS~8 Ү #-b9A ؗ9X٠(vW~Q~-L))VAAiwV9T9 yWLᶐ綂9)UZ'YH![a\!]8EbXgٕ9y_#my_l: 4vy9OZY~ܢѸj j9!Sut` xKB9m!.ζv~ՂX6o>XuqwG \CrZlU D76tJ{X8AZy٤YS:WZإmZquڍ*9v!lV6zm%؝#n-6o7VpYݩ Gњ\U'8+7Fmss:;Z @z 4 }ٱ[#RsBUyKOvlMW%! `6 \}h(BT @B%4Fi%k-yGI\B̠ڸTB{ IQ SymgCJA bx;ۉ[W[x@py)rnü yi7t|e|!I 7|ͯ"};z%{wɽC}W)[-ךwy}*]mDM}Oԁ< I릅@5 γs1B;śAiԯxؿ]Q} iʗJ]蝡?܃=ǝ܍1j1[bkn 7+.33}S7Y7]S4537}3msws3K=;>4Y>5Qg) r ){3|u~ @#r>+ƈ^^?w~>2a S~sԠ2jpLԸ@=V@ ^0 H q)  GLU` ; }]ޙ۽kqTHns_u + ?H ' ` `XT    2N @D_IL߉QU?Y0Y]T ذAРB8`Aŋ4ܸъ0PpI O$-aZI!.a IxrlyBi6jFL+1PVHٳ A˶[jeS%ܻxI1%ِiK\5 "K˘'O~G>$9`ĉSch#HVG26Q/cΌfΝ7mNFg 8V#z+6Y÷=,lo ?xqhqgQi.ti5XkPRu͖oLNr2\Aw\rQ؜S!JGt-eUW4}X聗zwގ`$ Famǘcߓ h`I2`&%!lZXUrn~#!C(bչX͸ݍdx= #)$~MQNYn &і\ebLvfmwQtB7gSݹ\q]Xhx F%}E9i ٥gY)ZфjFgj'r2bvVZ d΅,),h) mM(Z.)6.ՙK&YݺlD*.W/Ș+ݾ5w3YÖU{Wi+TmXcݥPҘP8ȮnPJo9[*>54^= Myn$f$ Lt[; fiЦO=EAr] fkV.sNg+1.%}ɯzk?5dЋG$ $7N_uCyWzǧz*ڸo,5Up; gCҷ pZVC<Ї{Ƞc4t2 q|]DAcan]kE/f Dޑw6<%.H(nxNTżEw(R%76 `2x@̂= |ABbc !! EaN# yHN` j *9E6Dd$( D'UILNAe*=N 0#h)UU(v_!O cϬ"pa2dH/9( QAMdb,CUșr$B̐%1x},G} W'?˒O?dB*' PVdhFGD,|iH=l !1d4#4xor"\Y4s)D_ M5 vãʮ:n!`!$ LbY8: C(8P&?˼a9 rr`l5dph?A5o]BD xYժ@*&1Q;ZxSS士łGP:Cu#ltxX%vy4 êwDʾŲlC4;VpK`&G6!N+4F-PJT}[9b!p K@>eXn\@xeu01 i[x12TokRc|o -2Ѐr!:my" YwBsEorW-OhŹuj~:Mu966#EypnsGX8xq!1Ag-Yæu3WŸp"+-ׄ"pk_e)3t|X4s 7>u `7o5`3GlgE3Z3@DМ aI5Ö[N6J\ WCU*}9=X16<4s2]X91!֋º7Bk69 n$-&+Kj k$d*\bmx9.8~MƼ1i@ Cc`:,&í}&XfJ)O4˛p/|#f/#ʰ21>sS sN3Y~^)YxGt{^c^j}]u_]]j1gk,m^l4f.WG q!Nx0PO:^cy;6Y~DЇ~.˒~Gn}YNp^2g£<:ii^87nQL|qrPQodsGs{fs7}WhV~0nGrDGvGzOH(HwOIJI4KdNTJO+8NJtL%hHBM$L:0(O@XH$DMTMNĄ$tM65 r`DfOSl<4eP58PQyU,|ȇsPR1RdRTdzX2 3s$5Љ5 G5phz'| YtB'HmIpxM^|NCW[fvgXٗ7gXo< 38tOVtvtՊV1+Qau0Tq0qnja'm1>S}ƒ^׍xGtgtp~{>'VA2X wm#V k 9Es' 9-%Gp'|)ŗWxGUMpQ.y#0v3)Yy0Jt 9{lg{'xK{M7iIP![FnʗWO\-i}hq`^3I(58(pfd en b8D{N9|~yi?8XZɘψvZ5Յx't 9ɊHPYUMii#Xh)ICPˈțIi0IB=W@ӂZp oРڠX 0Jbz JDRZJY)&ڢP[@P0.ڢRdP&j*VS>O dP&zIʤEM@1!>O@bXڤz[R:9jonr:stzo`J|ڧ |1r@.e`hv6 09j q&ʡ-ʩ#jB,j0*4JJ9=J@z*JfzI MЫ%JVZʥ^zZbJd: hZ@wz`cpZߺzꦀJ-jrڨ Z:n%J_j5:<ګZX_Jz ̚zKڥƐ e` : P:mHztjڭZJEx@p`8y:wI:kzZZn jZz گ.ʯ Z +VsXjg밳j%pz|۷}KXjQڱƊʬ7ʲpPeJ  J5+ olzO=P pРr0sw0`˻ *0TۮXZ\;Lõ`c*{ڶnp+rK8˸ʴ [; 0J% JZ5K7> ~kssGw;++ĻUW\뵕bkfkKˤj[Opšಓ;۹;;[\@ܻjڦz̧XE˻KOk?jjʨ!,4 @pd3 PGwGaۺ~H0tdȅ4zIȋd)ɚ5,U@L ʤȔd:Klʨdɱ 9\<˸LIQʹʩLPJVNʳJH]X^z̥F첳RPaJH,hTvQkP *o@ [SU8wpe|,PjQUrL`R\mx 8 @@j&Je[,{51\3L5,w8È?BD PlO`:jܠ [|,Хkm ǫKJ’j*%f[>z_Z:}]` 4\UF"[ +K -+ ՟JWo[ݧ]ݧ_MƼ;caf z֑ *uMj Ӽ$M|]~-M; ؋*j,[L4nz`\ڣM *E-ШsG|zPnzY;Ǵ=͋ qsjDQR㋸=]=-EGΪĤ0Q\ <ڦZq 4N]oq<] ( 1^ j;طܻ"N$n؏J ej/nު3jjߊl;=?.E~d۩.}-1䌋L;NKhP><[{ҝdNg&5pj:Z*x*ໍθMns9Ufz3m5=Ԋ]_ݐ-ji^:m>wy~i~铡 tr{Yy'uEޙ݊($3># +R0¬"/̐|% 5Έ=D0RL,CgTp: h $3r<7=#" %4ߐrI6ɂÒH-|2(2I# "4sUC5u])^Gw^`=ߠ2u.fXc.jU\ɢG;l %F8m d9hMe]ۗ۠qMCMݜ1jgI~) Fे% `Zb=EVӵ:Amޕc<^FlogVo)no6p =}iB7sHʜ$4IV9Y<&yݽ$M`Z@k,B=+)B~zW`=܊K})9Dw?֩|aCÏ(9яv=xA 8[Ɨ`O"~lG뾯ʰ'g|N"⿈x @2#2 ]A($1{,xb,X~B/Gؠ‚{-E-0@^yS*  &  &D b/>>>Ly2x",`!`N]0lL4*l-`H,-P!TTE/ܞڢ +>+[%0@C*x 6t71zCݱL⦚$>)h AhDk  Fܕ Hlo@N /T ` u$C C`)6 {G9p !W=0Z$.0N؂Œ Dx+FkxKZF=AG>"Dl H$pV9sǗ`Gvwx,ǟǚ,~>< H~1HT"|ع|A9FH$lF|Fg { 2@ɕƖd1pq,,GLl;D+`.-G|,}|Ŕ J$LdH4 7| 8$ܛDîܿhiBNHTdЁDPM\l HM` \(HMЂQDST!$LڛMPdN$NP̂<\ȧLɴ d ˄$dʹάAdFc11 R']3M8@dT3`؁@P'34HlP03B$ N]ÆKP@.iGl(>8qP< )(G@Æ20iHH Вz@B1P/E˓FƗFdV@(#rLuQIݕyGLH|NΦ,Ȕ⓮.A9HM >?U ȁ*@KQQqPtL H` 6] `|`} ᢸ@X}ZuU՟84(aᔘqY Ra`(%N.bqbN(b- $R`d`&a )b,##N~ 'cN'>M6(b6V 7b96F`д&?03bbVhEd$c00&[(ᰨaV`dfHd*JKcMaN6OPHbSTLh;NF~XVPncKcU7ݰXdPeQ.Vc>ebɍU$d5dEdPGVcp撰RHu^esnY6rVs&tfg\n瑀dxg唰[Ve|esxVM=d-~&zXda~愆fVef>%\ff6DΥaYOFV[օ0.]ehve<.ziR[i iN(g ifNcde=>hN`OfVNj^b j?EO$j0.kCCQv2s+^^kHl5u$5$NFkcRN1hfV& [Ζ3֞Q QdqvVVk Nm>6u7>@;==E>u-AB+o.E`视jLFm^hN [`Fj^c^꧶ovj~凾ot.NFo֍kʅݓ=gN -#qVFH) bc] ,~fFVvq%r'l~ag?h& "p*'F~,w/skppo0gf9W1P`Zbt"ׂ#, ,po2.GqePz!vtHoeIg#cdL2NOtPh&F&SsOfK?e\'kW En}DsX5-xt؛`E `&/aUumvv(vqw^_[_gf`u`r/X(J,֒(뼑' -xHD!LHsX~Iqoi[Gxtw]uW6gU儅%Pv}gؔ0>v-1ԃs0#8K>߁>hp̉'p`?Dzz uvwE8<•oFxy_bwykDW#͢W MtX+k.'7GWg~*^|{kf7y}BL{9Ā'N0|$L#Zp#9aӀ! EQdÆ pZ%̘25ɬi&Μ:wޤ'РBqj(ҞI.}3&25`Q*V5 ીƒ,ڴj˖ Y%ͻ)iqH:a&99:A)91vy#$(_zn ӆESnsزUϮ=mNJIժ\\֬hunt⥾S_NeL&  @:0 $,!L^pz@?[h SHԁfSTSUU[uUa isE7LzU'|w7Y ,1T,XO ԠoB(!qwq([q}XW$$bB| UZMX(ԍ[ إmc)2o$bXZ3p 2` 40  z$4 6b3]:('1%rRziIP*i6D꣋:KN*k*mj뫳k+iK.{RlB듲Z+j;mݎ+,:m^[hdFǛ 3İ 5(\m0$ >$LP2{h-| #sfLk´otpw!s5sJgqq&'ճGPCg /BLgL^|GunI1!Bi6m6q=7u7^KtJO1W-1s,;R0Y֏/1BU1x4D%}$z<E$:$G54A -cn)_Q`DBM`='LߐK@ oIKD4p>髿>>??oHzwC~g28pZθF8;hHyģT( @xt$@  DGHkx !'4@FCH`X.a(} 8$@<|0o blrHO(cꥱk@kp F~# )A}d!E2IS6eÉS2n.BdҸX!=8`2hx⒄< OKp #N( QBVhH2% q&%" PENAVPİM2p![0@N`RPO4ΑTp#' 9EA n:hV Ѕ24:i0R}xdB%™48N:#iJ1 ,ӠxF/Jt_"/JK&' @ ILzj'2` ST@hq6 L9s5ϔ,1%O@q.Ϣ@MS+*_Eu@iB행eX(#-oڸ +N T)$@>9ֵ6$KrB]lkZY ('<nxӞHK1 ieKWU%RLH L2kR^1bE)k%Ŗ9a\nxDDeu.<K~ YXw~u: 5SHcݰ< ZllR6% ʲԲ9o%; y♥+A w@ #e] 7t턩F\jMu%&7cs>¥WT:V<T^xp\xM~БSw#+wC tlg[ عs! dbhIK:N@@ŧi}Y˔,? (cB(82Ca@bN< >2C3 o oHPI<(y5Y}CAU^zGIb]E l8@~A   ˣDJ\JK K˹! K >6!L0!VaPa΋F>:!A\eUAKb!!!VX ^E!֠a"((JK  9\ў'Y]ظF٤M"+DkL@BA"Vt`}}%^'Z@ (n DL՞)*"k45V#*&,Ң/|7bcȜ8m1^"[Нa4cj\#>c\"-z7fE.68"9[V<@X@H'3^'aEҀF|HHHO $%E!$ DKd- dM&M$RNBkI&#R!c3rL82$$V _rgn2{RRrO"^E^B\Ȳ}-O.W-"jMs+ , #+S*22PPs3߱nG.g8o9+.ll(-P;sq$3^p,߳k34'4sW3k 41+#7k7g8SB\CcCO16G:_:k#c41kor4=I@Sd"̐3tF3(m"XX,K'6TLLcEM?M{eNeF Ե]5$@__cavab6b&,va.HAbCe_a[AfeWfC e?bK혶e Ķl6mvb?A<n[6j6vo#4gq"<,b@ ܉H7dAXw7`xyyz_[7|_#gA\6hz ^5^8Tߵ^uy e76fC8abK6ekek6g{6Chic8j!+rvmxm߶t6ovp7Aawq't8d7sG7vg8nswzK7y˷{W97}}7f7yx53x;eKW9)̂ eς8-8hxgbxkcڒB?"09a;z2Xb l@׺lrxoy-q9k+C_:as9Cw9)$$WcApb_߁49{Gyc_ 7Av^wy~e7uuv61a+l.sx6+$G 9;<ēB{ ˋkӸ{|2@zbzGgg;7#aA @@6T;ȁ_ws#{_ˡwAȁ}u}{{x̾{ |xa<nCS;r:'=P#4A ؀X/A 8d׋_gs߻ڻ;zw=ݿ?ۃ9C;@ 8P rQP!9 F(D)J1YGah!#W.5 ,+VH13 7q*[@ԓ(č!…̓DŒ2JR&d”IfNu|"LGa%I_A*1.Of 4PX 1P-Cb285 EFcdظCǫp2o !}-HZǖ=[yp}o(YxGB9MB@I|@C2=h |yа  dР1Cƃ Å4Ӱ i(P@H1ЌA$E-cf#R袌*(0RNP\Jp R*Pc E\LP[l(ϐ)䲨Ô*I: a&hG( kª G #'ty2KRP ĜSE+$e1ȡ &L$3&2<M4ҖS'( 717L=U(PUSQ3JM<(uVӂ8Ԑ8LUUR1Ed 7SC >n`{daH@aۀ/<͡2"*^"PF*)"z%j$):(a3R%)J:c(.4d&pL@l4ds3 -sYʐl2 +J;ʹ#V V5k9~UR_{UNk枋骻'ie; ݻX{yz=ė$P$%0]I`xh)EI!Z"ǯ&c=,&'U*0vr> t-F paظBG)l-ok[!Qcl#cK[:ꈲ;杹\w3خ-%kN_'`J"[+&3q]N!ldf) O`D"rŽ."y! qxp9̡n&;eh˝8,le#'egˋHxNhs/d "cԊCjs2+p2DJzb@{sCW"I0a#\JFtoyQaEQ[BʛlMbagEyyҲP HҍS&6o wE.<t!@IB'&50N̓7dDBC"lJ*Jڽ_'-@OR`Q"1J{֘?g>4kd[ܖasn@ FoaERrĜ#Cg:Ah9`Ν!K`~a|@8BPDtOBxA^ZMCxFN M/u.Y|m6ZۇNMb8 tK[iKcz洏?Q;WԴ~-IfV?Simk\Z_V ښت6vۜl.}`>G{}hn<萯ܔnvޭoNj{s߼޷UoY7ٽ5 kyy-q9Wo1{^Gun{/_7ecO/9·W߱^x? 'KvpBRxl\g#XҮNmw1LvFt3` d`g>ɁGşi6Gy05_E=>'ZVSؿOͨ׿>'Jh&g ܟ:TPp]ίLxJ 8 `bN섿0pϺ  m RZV|RـS>O?O A O@5 D,OFhjEF 6kOk6@pJVlоmOՀ-oj[j nvKpou[P?q;PU* QveIeߵ Auer=rUK e  @ s_n6gۮ_QwLYtTDRROttS+p\FT;vG4UtUT]'uZV^6fuWWUK__gw^@ s00Z9*aa1WSze%F3{{c÷\ .|dQ/}l}}cIߗ Ug~JJ!5avR * ̨5bcz .[91v{Eu5-T{T4;̃GWKIrV, )x_X U *0vaWnSz  /x[ 숣L\!XUNU?Ί5QTJtC 6 Vy̤Uwb?˴4scTA-3\Rz|HO^SK!ڡ0Lnk=iL =• =# $^)WuisYs?Wo zʳ<;W]rPviգPsk E[b` å# S|W|osܼ+k\bț \ ]۬C<ҷ!a/i/=;~? ̸Ѹe\hR$W>iz+M ϛۻYй{~Æ^۬ r ˅yOJۻ>Ka;=5{ %S텋 [Y~!=%('>]y'OY췌Kw[KO1\_Ha_e?=BAM1}T|Y՟߾߹*/ɋOm  0Bu :|(‰ !1FK(* lJ<2e-HT| 󥚘4KV<{I1!S .k0 TTXju֭\bJU.)NQij*'jڥhE=w!8};Q-M }& ?~q# {|7т"M:CPZ$l%_[ R$ɞ*Y )pU9<ɟA9 {4җNJ @U׫]sn5lezl=BXy߇]1 oK}9WE}-BGZԷYqRm NH}XL8"mz !aJ-p%wN(ǜPDGJRuQMUxuAB) GJ&< %deON&le^~bIff&hfnYpIgsigdے~ \$h.-BhOarA[(( 4D:[zH$X9Zk[kk¶ ke٧l*6!Nn$ނ)JVjY+TByvld*o,pt\'#q$ o jOǡwHdrxGw,ǀA2~l2^ivi.nPsꤽf֛I0FI+F-KrL<D!=6Ѓm/s*L?,-y|yQsdlj.$'8x4r, ~άn+fc+I EWD<$5Om>vPP"=s8Cn*Ci 0s3%, 0 ag3qS! g*%-Nt?Cy 6((E˹2R$&RK#^S%dNgS8x@IcJ5[tqySF4ăL K@$Q{zPmV $p# UFP_5 Zt{ $rS1k:P4T@+ʃ;{e(!ѱPb+)Rъ¢h'e}1ct4~CTJGdcL % ZЮ9Hf W4xK5n t!;醷P&[']W}7)˳O(,nWZnQ:n?ulTu[%=nT\AFPS.蚛AxŗPnIŅ. 7lk^Fiyj{+_+-~vJ/%IXm51&x"/d +ٕe9b[R+9wI/_o=FħȍdͯR&ǂ~ х_(a(^Heh~gǁ~gTHWx'8)+H}w~k臟}Hw׆ rh76@6 `2@40(a|0 1 ؀0 iqXЊhh8x8x!ɘ( ƸӈHшԈHXxݘhx(Hx󘏹hz oax2 3(55pY3p3XxZahz'+ z-1S|U|t'}3IAE9~HY~GCٔ ш9;9'7ׇO}L 7'egQS#U9HyY']ɇ x}闌'hj)X)yG4ɖR"$H$& 11*4F:!^eɒٚ8qhruAN0a;g > ` 9WgqٓX#ɛ%Ʌ9 1! a;H3P, @ϹiI y钲&9>@Bo1n<Iq:]:] DcAYz)24Z6zJ vyȕ z٠a;3Z>rP\1b02P Vjb2MVZ0r:ka t8J:1xzeЭJj*%1>ꑄzjZ]p V5 zPzbsJfZ2`gʱ !+#K%k')+˲-!;ebPb0e@ce ]@/kGIXڙ|:@zJ *p5*A4PZ0 Z۪> qc ` yj ` P y:@ A 4ɧ~5p CjEGj{J` d;h1J˪8 2`ajupz۔ȫ Ѽ; yڸ 1 6Ɉ; =*S+hx 9f0Ƞ> 9Pъ1`XA|:@_q8O 3PsM0 320h .\,\*L7 H8\8+̋.<} Q q@ ٫0 1?,/ŭ_ =ZL6hЎ 0yi Ryq}Kڟ}딆F<Ć Q{RLP\ʫ}KO˹!?*A ڛ̠56Uqɗȉ` K<<l ,z<{sZZ͟i͝ zJ ؄M :،=!;}cw!-[mo@֞bʫکګڭگ ۱-۳́Ԝ*{} ٚ:T՜XZm]ffs6 =_ pums7@w5q8`r`sU4z  9=؜8Pp b60ies0q rp^MP6mnn6+-/1.3N5n7-@!|ځ&?] a4 !éY: AA',C | `ͦ 0`ޡW=ߋ߄M`xn" [`}A bl ]✰0@M }n!1/a2.!Z/#Ѵ,mIANѣHNz N]PqK|k]d. a8  9! : 2 d`荾~! VP&.$%'&NI)뷝%냺뢬F-=^m[-Q\[M| T 00!:b8x2>@ [ H$O^~~] ٖԤ#2%Qs<.OȮPMǧmVa$AbB!ABZ. AR=AlFM=xBRT"rZ&$.;Ah X; "1m"uol~̵ TUm ^Ֆr@oD徛M1lᤠ 'N "dСxɀ5>,SǍ!jBH`I9Bp@S-Cɉ[rvE DpITK0Ʉ &HYiծՂd[qĥ[ $v7cD! ok0 cLi;B ipD sH fC$b[ h")`j ̀8NޠalX :1 BȌ (`5g 0+1 ,꧝^ V|[W}u}W}뷿% xŒy6kQ<zIF74){HSUZC~#$!Jx+\ շK1tx.yѡZ -#DѴ4]03ӠwSxEaQ )oy6ao -{IӔ UT 4AYo$[$d yȍX[T$yd4aA% G 1Uw H5>ŏ [I/ Ae-kY^Y8,Y?cs[Ytp}44<@6ՒGb%b2 b'gD:"юD@=yO ObA hA *<TEA5A @hE+Jf4VTBG0 BGpԠQp"dz'7J *P <#*'58*SbjUet .@9îzUc@VujPk[Z%Uh_*2 e)V '> z֞'Y҆4}hD-zYbVҧ JCSJ dxLC(t6}mF 24aE-)6F%C*\. dSKb"BVcU-K\jV M+ o]}p׼t}+Z{ 4P=_sd ЂV fYς$%i[0)b;k{ 1)\"7 iuU /{ <Y}p;xy Xd+՟OF^ꕯ+[ Us>;v;lGKҚ8nm?vxĺ;T7;S׺)nX VN+yʁȪ;4rHZA̤(IW-Mu XX H36g<8,DYsO;:ćN]sŐ)=aKǸQFm|]K5SWٟxsͫaVX?%k3)7#csidy/ kCtN}Y=T~YnO-~ۻ45}un3P S7@\uſkZ_ oH!l`HY360ҜFgkA ?om@9<)n:*tA SPDmZ۾۫j -}nP3 O( ~G g" j8CF_P~ݗO,XWŒ,'17 7/L@:Y'S:<:Ӻ8#*$Z DQ_k Hɽ9k󪻻˻z*96{k9j6"mpKq{r00R { -4 1{LMN +s+SB8T 2Y 58 tC: 66A|;{{AҶBAAʹڹ+BS(%44R>'-F£Kz{{+1B4;+2:3;'*9p5[ZC9_k;zcCxf'>7;,A(BT(CFD Fl FFo 04#$*$uv{'DH[KQ-.t1T; X<< :ָ)c 498a ;01C3kd,9i}ZAĶCA )-2{.G0H7I:JK$1)*4z7Mk~CU\ET+jCZ/5^8RگdTf4gh4i$KDԨ\ɖD$ԢtIEDx(NG+ɦ#n| }+Dʨ\ń\Ōƫ,r9H I999TIl$·˟۰ĺIMIN0KK*Mޜ(E/4J0l:RJ\̈ʉ ,,,J| ˀ"Ij ˨,;MI՜I֜Kt7wKyM{lÔ+TT̸\ET("<,RS#'r%(J-(h=*x'!)*.&إy.0Qb&h10iD.xi$x :0'B'Rb' 8#=PTJPVZAj0P P=QQmm  .⪜ 2RARR6bRpR(M"RR#k (J]/SPS.z3}5Q6m7Q:;S#U^U CT!2Ԍp&ExG#' -T T1uQRQSESU]uU7 8SUU=&oU>V$;XPև`Duִ`hu;TBUTl}%m.oPPquR=IU5V}U2eWYZW)`!e$cEdM ]DmE]Xh U"R,͈LR0 Տm5גMtEQe]YiY;yeEB[%^E>U_݈?5M-5mZE 5y#  0 x Iۈ M,*H/Vȅ *=|΂Pk]YU-و/>ڽ ݾ\?3e^S2Q华=ip 51(ߗ2h齒, 91Pr <)A X_=3@%%`=E!A酔"“KɔMQ1RSVKRZZq=STMUm]WmY}ױӲ]=H1(b1]#ֈ.[[[[Z-ZQ5PjRRfP֌]**SrY!ff&uY[[%ۘY  #.#1|u}EM!*+ ,F-=⦙1TUT@EaWuac儯a 60dN@&͈'5 buIJvKLM6NQei2^nڇxZn}Upue>WZF[vW:e@nUgmdGVf2h^if.d/f0TfIep6! 5Ttn}eYF6[9>aуo#Vde)&d&肦f^gF-Ohi}PسXSRnVa6fu9.w>ZU>dIf~:)Vhءv"#iSe%rFlEsurPv&iwZVg[ae]<;gQ> iikklNj]j}3VchNg>fUxFiy찥lS P!mA7Rm>ZvomvVj~cVWlf].nl! nVꆧ輮fjpjNڬ.vZ~&xE0Tlٮ@Jk$NJwkO`j6cٖjfpnîe ee~nB8(hHr-r.r/r0s1r؁XNqWjq)l mmfjj^i&woOBx0)OkotItJtKtLI4oqzPjsom'cv6$p%Y]\Ch$Agon|D_FwG `khvivjvkvlO6ZV5/uEIMl"fOӤGDH"ӽUT͈S6r'dY:*V? BL(u5vucb1zmX=^KE?-w$|ɷ7%*geZ@F +L_d$ 9V:"dUjmނkU{I슚ٻxjY^Ī𫕿˄pQkU00\i1r*"ŢO Ꜵ$_s(-V)5Ayseλ=PR 4 M߄TJ/MxKOG=N%VLв-f<^:-ۍ}ΆMHJk̩rJvSe缾&3+8ZkxRH5mV\^lDžR˹dWj޲ʨ .ne;Bڥ-8<$cyZ c* :: {;]0n]嫛f`@0 h@9 .a6ji6!'thA&a e. mDCKH*VQXQ @ * q x{  TSԡ )P3 t<@A;l XH!2 g!'ꑏxEp(p,c$KQ2Dek)+t$A̐!,-tbBZR%3Kd0K1Y_fa ݫ3oYMeK ߀IQj$Ec1 fʠ'C,}ڇ/ďwB,?#spQZ26z[}=M)ΕsM3:*0%ͦLYhIXcD5 Dճ{TٵWRy|+\*)SGv$xE0XZ{P drC՚(ʱRosLFUIuU IfR>*mW3y[f:?ua {\z$C̈́x?"8BDfԊNOsa^S [%!]Hn*e_{ 01 7QmJ8">LhKTsO7 a!]eUb5̭d·>fV0,@&?faWL8 Iw:- y ʯn1[sRk܄ % kyu}aLY$xBm wqì;g©`D5`+F6=#V{rci1cv{ߍ9? tB9~ЎIcﶖ /l*N9`ֻf!^وAj*~]Gg,u=`I CVc93r}~(W#7JȜ۲w,TVQDœ=!Ix®nvgWQ^ aU/M~ԓe<;ܼ<0w|"Y Iq *ˑl9fbE3Y#o~t!o{Vm%wvϜ 9 JprDW*7o_kK?Ӂ'ĺ? zx~O,z׶jn:{b9,wxCXr6Е7Ak-62#l1Ϙ@GНsp$# ov]_{Quy>ԑa&; {Y^vivz˻{oA&D Cn1XA1KĽ$#9J'j諄@f&MhZ!%QpcÎxX+7 -6GMAXɁB _C_ Aq"lAn(<0`$7yW40=1S5Z`S 6,AױA} 摅A 6qS{`Ր8 2:;<ѓ>Kᝰ uTI,)I 9 @ , 4GM atXm˙]XʁpZϽ[\]Y B)\-]G!v"ҡa-֢^ r `wX˩\X#@]PUAUIbVQ"Yi&V]kdZ( i@$]!SaBbaG̡aKܡ-#E"օ b[` Yȁ X ^z@'^ܭKu\@ia((6cc*vD:: N;^; N<<֣-cEctDyX$$y,$'4CLDVVRd5\Y'niAuH"D88E9 9"J+^,,#-֤Ma"GY#P%eIe[teT( )zj8\Kp@忈EeKeLeZM:DN*NHOOe}]%]^*^X__bc`JE VE<&DfELҸEȤG$ff&g ghhEPir=#k &E*md1H7fIo*:b,,#sfZn>"Y`gwhg3Ww&w.Y' mVead)6)d||d}}e~gsd=[e6\9 \\!jQA4AcJ4n")$GXfG4Bb)zՕ)\ M (`(雊A^݉XuP.#~(whҨk[xjbSvVE2@A琝T.A6sT >BlU%#TiR[n "T,Uv&B 蟆G¨3sM$V$X1K:+>kJ+Nk2ϬttuvG2(34FZ뻶M塕f^v^j^ P Xu_>R&m1vȒj!+}rƞʦlȒƬ,˞R@Ğ `z &m 2P4-Nӊ\ a7 bvl:;S4n*2%F:e DH@ hDj=4+ I ;9k!A<*A:iŀ.Znꥺ&Ʀ&ݲSst@G߲ 'rsNHC.Ap`pppx hhՅaz!&&E.*6. ;s sp;B .lC0rWq?gOHp'0'0j i2˼ +\oo6nqr10sC,1;7g"Sph3q jtꪷp𼹪Q&Rp&/7Do 'p` p"3p?p#YB@2O$sX2C1 ([^))j&,Ԩ!j2l_3Ѐ @qsO2;3W_g2oƙhipry31ѱQ#*5ү/'Vs r!'D0s8+#K2S2 44S4.ZsID3DVێg,gݮ NGஓ@܀U#2:=oW[:1 @ Xo\:4v HB_ TA[@,)o"hR8qEЭ&pK-V puWD5q3lY{u6nn# lvmmvnnvoowp6"5Qu#up06rFFJwMwuCuc6@xwwxxwyywzzwyapbb+7+3+?wpv,tk3#x+3xcGc!*αb4*Fc7sxzKk6D2xx7Ƿ7[,lAPށ(AAyɝܐ 99+y/9;9G[ySOykwy?9Ky+9s9y9c9yyo9ys9z':A[cz[K:+z3:oAV׎SvmG}O=̯{ף?'?ʣ2838FWLS#?'Tk'}݇ύ7ѾPʔk?ll{-$[?i8MvP A&dp‡#H"E #r)YH#I)1Ôx%Ǘ'Ic͊7]ƄsaO-yyLGa5J)SD. 5lU65Đ!cF dp[C6ԝQo޺s @8.lDŋlrdɓ)WVrf͛9+thўG6iիgjp W@߾zy`°"6uȕ7?jѩk^{d ˦mﹽɋ7`l~/_}ﯾ}=j ,<<[!N B0P5;SEېCZ܏ݓ1?8 @}OAS1m#9KRIĎ''6o7-LlF))sL2/4L4Y22}d:XS;fB =QM]F}H%-TI-LTN=QNANE-Tec 5|Гs[u>_t2b=vcE 4}hhYgݖnZhrt]p}hhW?cW.X C 1 &CX#0,8 2#Edbb Q8a5.K>9exb/A >Y馝~gjp^9ŗ}߬l<;v[<snޛﱱm?׻P|\WgVO:~ 0ZӞUjYҶlCZny[ַiNK0y<ѕt^vZ׻xɫ]Uw^|N˥Kr_6/} \` ~/2/%\J/f!1~70b)VO\b11]3aWFthF?яr%fe ^õpڅp _ P->5 RmSzjWըu 0f[׬5n [:.oY:ve;m^cmj#ܼFw=mu Vws;Ʒnr;.l8mpx+٥tK\ j/iдI^r#yYr<0i9ous"t?Wғt#Qyӥ^[0_wC$sG^viWu\aw{^yٽ^tg_:x43wC^߻9w󟿼9WA7=z{eowמ:z7^}Og>tG~>}7홏dk?? ?菻__v\股W@jfУKNسkνOӫ_Ͼ{5DM ^(h& 6F(Vhf#s@T@ʐ~7̋#4h8<@)DiH&L6P2 0Eq%5-⌽.dihlp)tix|矀*b2U2q) t"}(| Le-v駠*ꨤjꩨꪬ*무j뭸檫Rf2":iYvckD+Vkfv+k覫v+CX?%_b-b' 7G,Wlgw ,$-+;i2i- )<@-DmH'L7PG-TWmXgӧ3µܒ303Ҍ))=(x|߀.n'7G.Wnvu,f3o0cɥsݢbI%PB$n/o'7G/n(ҹ*2)szcݠ>I$ IBL"H;>‹?/ u$$(&1qx (GIRL*WV򕰌,gIZ̥.w^r(H!ICye,%ɧ[xD'BjZ̦6nz^JbVGo?ŸTWFĻ>~զ8av`a4iF=.aI0LB<'5Ѩt4?eR>~ $JWҖ(┃!aNtʢ`kV-j`9T΃$g򫈼`{R[~U?2',pa.zyQsbB'zk=gKח dzַ0<}V8kL`Qh||s]<7?iq|ĭy] +w_RvP8K5Arx8== la@x?s^CwvG5-S lp6簏N5~_y?(!-!#|Z0 '}ӗ Mt}}XZsj~v w~s'{zwp p[1 ['uppUt:Plp3FuуB8̅}wV!Gf⇁wW;C7:>`xkPZqt^^_Mt@$|['^@uwȂD|8eFrvgn#%fktPAu=  t~A7{w{IHj`g^y|`)Tڣy_yPZRʤzcc}z^ʛJ:Z*ىRiq٦oʌpw`r:Szګ:ZzȚʺڬ:J1anj6̸ jdfjj=A>IaM@!A욮?JG*zjꊮ +k;+;K  j'){&0 2ڭ%F:+ *ZN! K@#k[C[EK+>R˴ʰ :cb;a۴Q۫*۶aJ^o{/{u 1딩YL 9{<;={AikjJU;kH!W ?˵J +[$;H emuKKP۲?ۺr Q cR y=۸KBLc+˶;F+Kջ[[固x;+t+Kk; 󫷺k 3˦˭U)rS⪸;>k{۵滽\싿[f%ܿ' A˺k$̻pn +‹J|(25Ð:Ы{ԋ2,5l^0ܯ; ܾ`̸G,<۹q$i? Gx̵Vo itɌDBWY[̻y .[Ɠr|ƒOƏ3s > |ǁ<[EȨzRȌn :KYu©OƦ\|K-UʝFB^+B~:MHFGQڨX;6֧ᨽr~a樽'MߍIa>0Q,.߾h@aFWF㇎ ~޲hۍ[NOZ.H. N n hԊ}$n'^ν.1nZ..٥gF쇾 G>.NI Gai[>杆n=b.vpr^wyv^x-m{U<O?鏾מE~?o<zڮ.e.]K25`snuܾn %3w3̎">=NC_N34/:|<͑w~yuhd$Z[XÌ_=..c?g䰮(ޕ.Zwry|ANKԜ§ΚԬ?}şLa]'϶MZ?W빏_ҿOUk,ms-/!@@ D0V .\P@'RذÌ9vcCKbH-]3!D5mƌ%L5oHQG( $jԨSH )jN"XP50 ~Ї>"ĈHT" 'VqS; EѐbiFDR0bZ#LX7K!4IMV;}ׇ>rd*UJVҕe,e9KZҖe.uK^җf0Jp!|I p eX -@Lvi"(G;vS[ld89NrӜDvCc4Kgɦ'}"PKn#tydEeDGɝzg9f1$!; UiK7zhw" n' !QdԚzA`cjҦa1L=FNNf:s -j ZϢyRTչ6WT\œ՘rkZM[MU ;OJuԴ'a僬:ϋ갖(UQת+`GWpIV=ER;{ RRchl ԚQ墚JC;W /ӺRկlV7ިeaivQ!leVF=\}#,MZ^)K"7YsKyXZVeCA\LJ􍙮TGÑۣiqW6YAV @ش5Y$Vu+U}jTԌ,\XD < \kvmnwڦOG=nrFwսnv-rB=ozwo~xo2C̖ j y#r=*X:'{w[9Q}ЈC"{Y,覽_L]F7Ȟ/R>+ͭ!x; _ HHm=˯<%uPҳ>c=;?q?"?1DqҽP?ȋ X[+ҹmҿ:JpA$(J@C@ T@+D沽 C;ө@ @>3I0C0A} % A\c[ &,)X+뎗DcH[6|CܾCסC*Bx+ J' <4>@l?AB*RD# <:LrĬƤ5 MmÆ'@>tKΔDI|MLb3:Jͭ$LEK񓩌4“Ml kMLĢLB̋\G8XNƩR09sQM#0%ܔEUdܼ}\[8ܫ'Z j\܎]]E8=ݷ]]mK,+6Zu$X p"!ϢV];-^<=%A[3EJ.u^W^HeP^AN5OW]c^-6N]W8MY尻[5ہUE_UVF`JUqPa઒.``=镬[Еn[`VQI..$~ZmaPU .bD=݌z{`'V➵0c(8-~b#ަ_ Ȋ E#F8P,h%U4ca,.-`u6+?@EfTQV !HI;-Lc<[:)ºb56-QIdTE6d7&a6:Q#^\Oete*˴\]Dg廩_^TTѿ FN5Vhfd305)Wf:Q^if闆6ɀhr 碫Rrvh>D`;脤Vfv꧆ꨖꩦvNj;+:NDFAj몾6~jvf'm6ho$&Пvrb]])kjjj~Cd7D#kPjNjvj̖lVk)FmN(MٜU<g N!tؖhq9ڞmh,$mâ' kuh3F.n쫢N3n"FVQ̸~Usrnoo< Vp 6CbgDmE&wfKX-?FmOyooo*aPeh:%:psft|~,QۼV j=.:@!Nv m}k<-Nﻈķǃn虭[=GOm(*׽{A{!k*c&QW-DWH v1 fP_:P @fV$\#+ܰ$Dž`Kr,z|`I(j;wE=s8 Flb4)QI^Z=}'_\%hzdIe O!TCO{AlUv]YGu,2i z]SHO";R;v-d&9+obĴMeSDV*V!LbD)*9|D' TkzDVR-`z5P«r8NUa,c>:Sazʤ#aX˚ѳ;b# ʒ׾V&,rP<!5j!}Al ?X_EÈc[ֽ.vrTTU2 QF֣jU-Otۆ9u":ݗjؘw+Lb"[Wc%Y\Ja 5?ݖ# ޤJ@ş|o1"_ U%~Mp~OlQBVzNz?d}|%l kPIS|y[$61w\f쮸u`,$t g8=1V Ʊ$wRA/ U#Ó,.GP6iG)rVQf#'^vیV?Jr&F݉"׭̍/tZ6Ig6]|lT)#7xK M+)YtZ5l.w̪a]V 꾮K+Qu6$ngm0 x~oX=j;L_sz5+rWڵ]̿{wެ\[U*pmH;u3Ti{9{!|Yꆙag5KJj1Co㻚k?=LjȨR?uM^h۷f{$[,~3}W^I$l/GTJY<Nc]$,15=A":}Uy;u*~:#F$11EbD"/<0֣1> ??z@@z(ʜ\B)CCbf,#O :n FR$SI/DGGcH$2V23?d,dRSZMMaPHb8O6P*9$ ΢[D%<"HңS#LQ仜d1beVFP}e6.XaN+0DPe\_le'dJ#R:eS>S_O؀ LeUUV ܬMe.ZNDvIghm%q&iFjfĴJX'Lfl#UUm")fPŭɤ)F&Vj@$q:s"qvg=]ZJ%8H1LP=8vfww&V4Tz%D%[x(O`kТ:g:NL}BgRiVɈ k`g\hg`"aʠaR Zh5VT8B&$-&E|%"]O~jh"%I(K`h"[X2nbE(NxyR2nhZL)\T鈾eGhoŨz)ꧧlq($M 2(l:(I )鞆B)Xhiz{d\kL!zM~*hSis6YGc;#}YvB郖v"j&B'QꡲNt 'En,FjnL&i]bc='n^~d^+)lkHtxr%꠹֡*8~R).*霎N k8'j]Hgȧ@A(08)뼍nzz}zcjǶ+e+Z6(h#jGl1 -5@k֭N+}bfgׂ/Rjv Y ~Hdjjg%lfmm~+.dGd)f)ngNR!:\JTn^nhAڌ~.ff BtHQmz%6ZSdBV-Q̮ffv<.ne鼎o^p<.nrm^hcn6>鋆l*G2*,߭n*;د8x0nGh.)5/^l*c*WG8Co+kDE:D,,+P *r 0 #"e#2WA3P2-F@D'OE+c4UͩY lmzR+1s-3../K"1pkq«$B8tndr&5[TFcNqVDZ'H?HE99GIF;J-ӳ.s/ ):NtOoOkm5HZu#O5S#S;OptG,VZnu rM皅}:40|6hA4B.3/ALMz\ `]O^+1^2V)`0v[Rb#zUТ\H)8[BwUvUf:Uā B#@%P#Z6[c,hkO˧5_'CWQOR׷Tb7v-t2tS7ܲAXrЁp-Ё%A$iw2<30-B,(T$  &DK8Ku_;llAmygc .Ӊ7c'wV-s_ĒXuCS;YvHel8ÁXBy81{%%D#8"; 8T7A@3[VwCnt`5lk:TqKrkj'zutHI_#qA 3)A%wD39By'+߫Z:6 8l'(vi cztsK6tWw@fvǫm,:x" !K +?{D;GJxo$Æ__:~?b{Dxcc+ߗWY;x##43##<+<7C#43W M{\|c:o\Nwmk<<8C8u2;Jz1$D'/|;fAt88ջ|;@H&D MbЀ!JDuJ@NIeIH)c!.LGJZ|SL;:١Yg*?:48=mYs˦Zw3gQQSJ= kJKbUجfiͯW{> kԎs͛Vo_t׉86b%VXaƏc<2d(yb"3H+'iyF >7 $G!D-b#H$Mjvɲ'̶^LPn>.Oc*ӛe{,W\Swsj`@ C,LdF;ҜJSCՌzMd+6Gpcbh.h:(J:'xxzO>+2N<**@,˲%͚r-srRLO2\<l3- 7sPΨ{@TC2]ā B#B %p@^ NF9k&{-.H#25HT >鸌R$rȌU-ve/Ube)[S6M3Y4ߜ3Ni ]R.۶s A7,6CJT۔b)ѠFaH$pLq1]%uTW5սkX;s=(Sm֪̥,XaƋf|dE0Z;kCs&O /[[p5݃ RFf$rYQe8jU2)ԬgX+`jj'5 Y<n6kᬖ];ЖOx,13!`Bu2hMC j_:^XtIg+[Ho;neOF9V;mp7 gЄ(# 5ސ=Q(EH7i~=m+b<m]}翺d/Ɖ6݅G$M%N ̐A]NX7Mz"=iZ@%$}^15 )14XgC:MA-ORJt/!`;!=ٚ`sAdpqң=y7 ᦔ֯O9m*է~d_[B:f,.u| uC!-&.Q٢b@=.nf_CHP`F1["E% : k!unaL :J U3H!n y"FȋՎW\yZt 0P3LFu}HycRv>b͎4e<79:<~؉ 6s1i.d-M6HS6yNc!:X>ΩЖ>O>ӗrGџ@AfVI#B_f4) fcQѸx3(;ʏ ӃP7a jHpɰD/Y2EuZut՝]TS'%{_ e h!v_:QFc C̀a [\ Sv򥼜)kٗSmOv* 1-h!K5 Ťf݌KSOS+PXՖJkeZa%ms{[޶u;-bO4ܿɩ3=nDfr' r;]v71!xL^zJ&  $moЈ.d~,W 0uFf7%l {6fp,R: SG5q<6עL'Qsx|E0;>q< <3P"yIJ(?(vݔ/j(w2{uTSRBmkʹt#_|VGhLRyW樀{ <\͔Zkq&i$9ώA3lڹt=Dsm4 |aZҼwy&n4Q7҆#L*.6TmV%mL;/zjqp6gN$a#[<.fr e{cSLr e#z32qp[u4'}\&9|x3ёt/Mwz uOUկZXꀵr//1w!X𓰸V\T?}muQNh:6WZ=oG{~/{ny=όmYfzWN|SXs9[y?kp]~~Û|N˴.sN (bįzܐ,M00/&-</."rq.qg ("EJ ք*x3l?/p02 /ToPeZ/pP*&k2"@ ]  S"x.p lpe B#jd?/ Q/0np !*g Յ2Z  =Q&%FQ&<尬MPY1,bQaZdp0fQ*v1, wK! )Q*05 !c; ?'Jq pP EPj 0e!0'41IQ2hWq0_qMzQ"2QY"+7q$P7d!Oqk/B&S1r.1%Y1!$"'/q.'}rJ;2/>$" $G$W%wY16Q 0 RUr2MR+r.r@R)k)?RB* Apb+  [ b,U& RU2/$.!r.y4.w0)4B(MS+3s06O+2,S12Ӑ2]3,2 9-=39ӱ99s:j:R;;;s i67K7S%S&,]&7!3q???4@t@B* = T==+# s,?&1&?sC C=CA4DED tTCy=q $T&*"Bs-r=GtGyG}GGD'EO3NtEs1122RFB&#'"+4KtKKCHH$41YT+z31~I%3>/B3J>qAs/6`BOO"O PSPOŁPAQOsQuQתwtL#L+q7Ѵ=qR,@,ktNqT'9u.O!MUOQVUi5QsVPgPk5WsRŁXLSSsA4IWE,c kFNϔUXu[WW[u@l\uP5]uXXFTY-USqUCZ_R s 5-SJ/ ]aU*`]Ua\UaD\?^R^;Y7UG;A91ZaZ-53`/q`bQ)?pauf5fcfqv05PfGb(c݄ccMdUdP_Q>ZUi`b/gvbv]+kVYv6gf@Zv^6iIA5djUe3Net[Ӷ/taulvWq go dXn6Yvtd25jgUV`e7klk\ŶquW@\/Nm1]V^)rREnVIIsO6oS%UCuyfPgjWp`_z7Vvku{Y{BR)w0WtwsM_WNueCo3K~~pwki7_w}6%@ XX(eow)- 085x9=A8ExIMX#y5w2(]X؀ig88~Sxn-8xK2Z}3so{8d8}'%vZyVy}X' !xٸ8x鸎8x9y 6Mc}?bv،w)-1?24(`tF'B"N17MQ9x+R {kkBGKYy}G)Õ/e'l%%py9yGF'bSSuyɹٚ!yEٙwҜy919y%}9zWC#9/b %z%-1Zw1z@b/J{WvS:Y٠[eV l @yMNZ/Zlcqe6u]zK7z1$lp  lZl/›/ŸV" W7mkzyiZ&zsYB`uUUO[5[V v:%{@:1zٝ;,{ġIWM\{V%']n*;.Z9{%5X+b ""a[qUa ;{8Zi[&z"va~[{UwpU黾Ѥ5y1[%:JK# U廽5j{)|twE;۶ZdO\QaOzck7++ۣ73ǭƁ<T1|k<3sܡ9Iv<ʥ||=Aɧ<;9z'y ʛ7Ɠo;Й9;=);i|m\%lsz{u<MA=E}I;#:Õ<CQ=M\WvO{׵ų7؅}f{uVי=؍س\Cy|zIרeר6u۽]YWQWkw[qkf | $u315Uo}Y;]5;h1k=h\}bZ+ >E}Hg^x^Uõ]|Ͷu~G~=kV:U]_^/bc^BzƯ\ґ}׼ߙauؑ]zE^I>;g5YQh^A%8w~{ܹ|ϫݞWWA^ٽw3%a^2U?S}'#{1g]ޗ=W?R^ݻW[6Si?!<聟1C3_S^;\hO^*‰Xgb<0B:|1D 'ZxP@q qǐ D$A) 3:T L T)ɦHFSXRڴ+V_ʱ٩Pӆ][-׮jejUNĸ"' .ؠƠR4 J6iViN<:ѤK>:լ[6`ىkmbId+q^>^YN}~=ԫ[6#nqwGw8x唘a3sۿ?&^(BǛR-egYr)mPg_nae_"6EbԂ 5hM `L01DH!raBId=%ڦd'6`DQHcJ,HK4Fل>^%IUfnYrڜtfgs)'w'{Cj(l5KhO' ^._~ қg'ֹ*+Z+k1HXHBh 8鏕z"WfNgxjJ-ޚjb۪r-֫Pbm8lŢ"8Ӌ6KBB G|+!l¦1 ." ޼Y2fg,Lm 0;r̄Zi\ y[!ߛ:oBfH,L5KYc]̮J;!g)B.΢Loʡ>-jS˼^ݷ8wE6`hM"‰oq,*swUs9գI碁np~֥@#${m3Vč# v=&0dI(`|#f) om|7:bh?#&=dXD(pKa 4` $Ja㺢Ay Ft!j#H XE*ˊ!`BҶ(֐=6ьjtGbP,w(D>Q:>wģG]{ "!)|ܒi%'iUSRKLh6$R+OKXn,Ck"K3T3NLRst) u0r+\v7P&W;Rr^\#OolKĿmo!p=.ކk{>_x͌9WaT\n\3V h[cu)62qEMuMyie3׶XNs_0׹ub'nً~=O_[qWzþ2}.}s/ݗ!}?|ϣ}JDOz_k~_I}ݗ',z7a|7|7|~|`6z.4xTq(]/X ׀Ň|y7r7vǁxz芭=(GBPg3iH^ Ƃ.o~2Hgw(}`(؈g{+QSXuhȊȎ(pAȋ؆84Hhz\h(( Ɍ@f[xtR !k(QeڈJveVɐx SM4Ʉ1H'|DybHc)YexB6PB8IȆ<ɑ(AyZC,ؒ.u#h&$SDgiV8`Ӗj(`vdyd1)l ƈyFtdcc{Ixvhf9-XؐE9seɚ)IiSy.Ur~gwgwt,7مp)cIi٩w yd wɜ]ɹ v禗jcnɕiJfў*ٜy5ޱٳiTTyeGňtɇZ}oyr c')!jTjwHI 1JQy-8JNєRAɖ%[%_^f7ytʗ)ř뙠O֠F )+*m9HG9aץqe)1sVJ:^*z {beV2&צi5_ک癈ڐC:j(zgk:6稡 jB!J;zgQI< *jC('gf:"ṟ_U :ZtZzI}zK5縗A*Sw;*Jk` wDwحY\eInʨK1 :Z]) KVȢU[:'˫˯< *k˲% Ê@@7+&; CcIBqe!z)0$C4D6+ka>c[\kÞBKPG{yLN[$N)ETڳ*~KkxBkM&U+bK=g-*fjkk(棁 A+JѺK5U;[zo㊮y˻|;ǻɋ/˼j\E0 '˩銽`K<њӺʥ[c 0'P*{X뿠j;9k! kS24KktKh4(é2íRr q<'Vdz雓/>:|yF"SLUTȨKIl:KjcLelƇٰz[\ivgLul}̛}G`VەwLȅl؁œƩ+*j-mVx|dqəmFiƒ{P`ɕFʢ˗˺˿ɻ[kH&NMIq&Y<Ȍ p_$zƑ BAl̥QϦQФϬΩܴłJOlt mh}бнLHr袵Lɖ|"-}@mBm Dћj<ݼW 1K%}h}cֵpaLom+̪ǫ{)Jp,xrbtԆ MlHgraP׌ʑx:Œc=ɏ-fmͨڥX,ݠ}ܪ3=ѰZ ńw-=]śmMl :N@m׍٭ -ݍ&ײ,) qŪې]5m .Nn!.#N%򬢱:-/,9;h =NEn<ޞM`|GQ.S}=q[ྍS]QnL#6=tj1*_mqlMlb7o}] 5 س*@AGsE 6X^.|a38q.>tdz>$E8.C uDNl̛.ľ䄮,$컠?~{uϦ{_4N(3џ8ޫkݎ^o`-w!T?zr=ğ'Q-zQȍ4/ڥ$-<A+{c $XA .dC9WPE1^ԘD~BIFԂ/ro/q4IAN Tu,tG1׼Pu6J˪vngE*"I\bWUf=YxV}UDNe@R V/0df*L%l\sr)ݫ^;k"״ʝ q+دxOc-7]Z5*/5̆vС,21&nRһ·Cyos; Ͱ)Ulv"ۻжC=ցm~`/'<Cxn+Հ}bu"4ͭv+rw~03lc8$-7v$.vOlO/cr&v;BZ3(xdArE#x%Δ I]y1s`zBJ"MHgiY aA,g9dΡQru|ojq ϯ|69^#1!(#tqyޥxo[[ֱO^T8(q2_xALd+Czǟ{::O p?N-#|ڑzqgw:H2g|xFR:̹As!]*H̬H FuFu;5}w?Tߵ Oo<8+/unV{Йvi{~a/z~__Mgxan;f>@.ߐ;g c/>ݫC@Y<ȫ@#tk?K>?K6[=f?? #@=Sӈە$\8ӣ+i@󿼓=(A ?LApc,s@ʛd7;A9!+(4q@ =S$<%佳B)T*T 7/;Z˽[&'LC6$9\`Bs<-8.c=4Dk #EBT2$T,xD8*C_BKr3c2DPO$Cq0tdADE,ŏ4X<7DZ[$-#_`O\C7lCcK1+3#ʓN<}J~ľKDǛJI3K@yl3zEĉEL:QILHFT|@`CL)I<|H{\IlAȜL4̍7HC.ѬiJMj{JGܜ́͂̓hE ň\⌽ȱʌ OHNLȲδJ|{ntJ<׼͔LId L`OhM!Mk%LOD<LMLT)!JDK<\lLUeM Q%lPЬN;CFbOM#P$=ҪdTO3s 4`46۪TQ.(|1STmN4}N2RIҗL(X&Y(.UʽPlR MS %3-L%7*8OKDY⛇S"P|%T5uR\SP<N)c dTRԼdŭSUZLU3TX@iL9<5JJJM2 Ԥ kJW֐EE,]9VќJ_U1V#U5TBWzqV[n\S.f$N ^ Y||f_FF0[1em>B2udq~dsWfXf]qgsgfg2Q.}farhsN^(a_zGWܟ΢>LfQuLNVjxjy_ԩjjОFa ? 遀khjNF^kdjk2DjjU&6NjFknk~kk.Һnl~a^XޅklFlvF.m>m5R9kllmmcWiᮖw(nxw1^tjKa6.m&Voo荭nnN4`P2 3(2 oVޔ8o{|ɯZ.~V |O}Շl?=n˷cR,|}}iw^n|mF/~?~RG~~i`7i~~g좞uE.~_ίP2*h%v g }] D p!CC B|#"É7,HqcD!nq$ɒ&OLr%˖._Œ)s&͚.nspg|'џ#Es@9Lie*)NJ#C +v,ٲfϢM6mԈ:)0(S*ū)A 7=ua!klVƊk\ 9֎^bάy3Ξ? -Xr +PoQEKQt besLٷǠ#O|9s{v]HC(B w34^y1X Rϯ?X" 2ؠvQtNMjحvNs E.Cgy'zé_F'Ys3X76B:NSuu!w6шD`XEۋEe,☥[r%K:*ci=D݅J LtDF(FHۑw>)ΈNyc[pc]饢2hr`'ᘱH@DF ADE( u]!D&m9ZkHJ:a8^D X'"˂jzfr-߂l KQ;E+䴿6el9M ٮY\BtA"kj\{i N>1'<+ܲ/3\7s;"<sy/Q:{+- ٟ]tJ&5&jDS5IMo-6ف c\9Ym3C 1 ]GOXMQw".1ҠO]5_xSx m ^駣;%ܷ ϨoK-1"Z;{I9bLƒ9?[| {K?'g=q{ P51cI۞Y0ày>g-H;?XΆ6am&!Hؼ/C5Am^ &Bq'OM0 oZDJ;mdkc`"H JF"pQ_8=I)F1lb&vCCJ&INt V߾HKzC(y򓚔99 n#"38@)dDXJ]*SԧB!R-Z]%Ur"+P]ۤ5)*0{e'X- -uivfY Oq&J p^JWr3i3a&iI2a }@*8T#ԊҩӝOq4$O63n)MKXr|$v:>tnXTZz)SJt: E T Qq}dN+Y'ՏMi}$ ~5)*Z\ӭb*Vj¢r`Ū%؁5 _K՗1R|'M PJ2VUX-k[ײCMfK[ضխly{ۙI-R)9][+[z<#,\XPzֳHv d=Rע7D'~+;׽1o{k_o~ߙk!HdJYfbLhAKr(5-jz􂘵{[b)F1L+,~ɀucN0.T,x@ 0a vBl%.o]ev%_cɅrQͫ18&%}2zؼ rGd#M4#ObBT'DSYFﮅ_ &8,f›&7Kffozvmt~Vu쀆W+etFzk['zn&Yֹs\ǁ9gBе7xK%-kӻK78'^]QpCGn~yg^0x#BWSz7{?ڬwaԅtcn=~p2=I/}GO_q׿u}^JYu^ɚiq^A~  r q29 ޝVX n`  &!!E` F.aY= üOT,Ҟ6U!C<N!Ρ!X\!"NmL\e"a!!#>B!!$V$6%ga`,⎅"m=!zaQa$*$J&bJA/bD'zaڠ"" Wb>)V+6V"5ޝ5-*_%#Mcc1ͽ"~!gu$WA#A &ʢ8b,^,f"<֣<ޣ=<ҡLt`(:AF)Yaa @IIM!#:b>WbLF>vGDHB$0JG|0^8c )d0ax ^#+Ƃ+^d9r$66ΟST_J*JUZFL* Mz21Vb2EJc2.G^#[f#S\/2KVJ^eKj@!_e0IcCU$bZb&-b[>[eSD@^ebeZ&Gl%ΠW~%9&eR&Y::""Z%=Jd?Ffdk 7>DVj&^er&UNܤXi2%R*Rfcn#m tBt!Uf^dUfn~boUpx`>2!UrF;k'uR"|NdeMXf`}'hMi;z|s>F%N}JtfiHxꒅpXU:z:\gdRfvƄ"``h a*#!QeQ&j&>'F'sh"1.'iqh2&.(6>(r))vh.rf6設%h5ji t Jt)hU) jzi%)VgXLHRjZbjV@Ji&zF#>蚺)h:2*Fjjr)C~nJpj޲ҮRf>-b]&* ?0Zmo[u0B#M$[* 0 # oD+RK(q~i  SOP//J C0N0.J1 op0Oo+,o/b.͞f-p!sk !ǰ`-ϮD톰]U1&G-B('#Y*1l.23r33/3wF.20G0?*>) O-{Ks92/U6#q7pԒ.'3s% Aľ)62rs'DDp@Es{s)Bb-."@CsFWtKG?Dz 3;[ ˧&3k?+,oq@wtKtS7GWb6`7f'aC2>ui'V#s\_-sSro7IPw|˷uOMvVӋl#<6n{;xăP|Am«悶pbuJ|'?:߸A6O6+چ=м{0wcOLjA5T4M8Es=[Il#GOzWzh+Ay )a5Eg@a+oD7']gwk3|=wg< D7}&u}M,Խ~*|wZ<+~8e>wڈEkWS3u9\0p7|j>PϽ(a>o4ef[<#>)s{_9>D,}4xA.\ DV(D9NB!I~,IrɎ)U^dٲK҉86hPg5ziҠD0^~kXF [,WN%hjŸ1M֝ƻūoyăoTs'TN|YiN1ʰ܃Yn5hӮm+!7@B =D]F}H%J-O+ !KK4E<TU]U|'OQ]7^``zzAwp]]Оê~zbKz>*j}׋ > T@>)XA ^9A~!IXB e| B/}- W@9yCAXDP{+da_3XE+^YE$揅 (viTF7q_ԟ IILH&I  YHCҊx3Sb#MwL@E&9IO~r4DXz"(YJW(UH:2cal(K_&Eze)^є4B0&хTfWf8YNOҒ>o%4MhD)ZQ^F9ьz!ŨFEZR*&iM_ W>NySiPZTIUjQT>Q5jSZU^XVJU~G*NsZSTpikb;p YZWUw^W|k` [XEbX:ub%[YU'J븂VaAZUiZ׾lin_{[p[\ָU.l}kڳz@,[V v]~x[^Uz^WrDfWEo>e\. =p7²mm³_IW/_!I\b)֮[? cI\IblъjJŽd"&1M ,#Af\d'9,evM3<*&Ӽ3YZ~32msɼ:y:C~F4Dё&,E7͖FO͠V3X>%ANb$-p ,b[׹yk_0~aGVlg?FvMlPKmln]o{V nt]o{ ]p W8δv _)^q_漍8+q|m!2r)WY~1Qr9wysAzʁawww;w|EvWx?~ <ҫG>yK!,<22BCM$̂ KLWز,,8((433?CDN..:"$0fku ablH @Ç2lE ^Q ECB$D,IФRHcbX@9eس$HA=8ҤJ%"jԄEjA' ,m۟-TҨF\bKnƿ/&fX(qL3&>` cme $PHCdh@(E 6D0 !,JT422BCM$̂  ز,,8KLW((4 33?CDN..:?@KablH*\ȰÇ#JHŋ3 @Ǐ CII;\ɲ˗0cRQ͛8syPeM@ JJ?*]ʴʣH:JՄR^ʵPQKehӖ]˶-FQẝKܔuu+7޿oÈi86 eR̹H{MҨSװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k@+,!  0 #ew$"3QLhȈlh+#t@s`H@L$FNL(P(@n0!,>22BCM$ KLWز,,8((433?CDN..:ablH @Ç#2lE ^ȑ(v)q`H( )2ˁ+&4~bΛy 4PDiNH ,,8((4CDN33?..: 78B ablD@ @ C\ P0b9BH0ȃ {)QyeK"rDzyj覫nwNП1Usث.HQ"\-m^&۞n: p.(`W#B$1@Hdq&k0,ҕ*>Z$t9o &YCiB` Wjt['=BQ $"c<Y0 ')DgӬ|iLPA*ߕ̠ ue Ǧq䁾(U/Vbe @Udrs'nH#{~Wo؇m/2TB/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKͭnw pKMr:ЍtKZͮvz xKMz|Kͯ~LN;'L [ΰ7{ GL(NW0gL8αw@L"HN&;PL*[Xβ.{`L2hN6pL:xγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNWVհgMZָεw^MbNf;ЎMj[ζn{MrNvLp |+4p ܲRpS 8emo+'8J$>YJ35'·Rr&k#uBsۜ?7ys\IGr~*IGRt|WU؝{W7z]>va ó"ΓfzOx.1> ]ϼï; ox[NǻGOo-|Og^z{ˎtcЃذ ?@JLvN}v @" 8 *L˄wԵCW>%4?N):|7?!'dE@|d}v}[G5$$~g X~$yglg3 P,؂..h5|#88wsCP00}H~!w}(wICqN؂DX6F/OSuP{$rZygBxЅN|=779tuCi@|,|XByI?ȇ~w88BkS/DHsVyi8|B\ph`L)o3rBMh,Y7fxd';D GL@~0XqyWa Ȩʸ|3؋vicanj(hGsAȀx~L8P7pB׍7 'H)D~H~ dq||7}p #%irxD7{{ {_05ixq9 *YB1R)Git;q=JECh4ZYfpi\IDZ`t _PXmIȉiHs \ hM9B#049wd]q $ GpQd}ؙ)9Q w q()Kpz rכ pIBGzɜ0Gi[t@:gYsY$9ٙ )JGyy;7pIuɔMX֞JUV8]ןչvxrg1goʠݩ'*X Z>7n ,:Mp1X3 'p(XCʣ!$HJʞ;Oz.*i٣PyդVJc2tWZ`.Jh*v6'TgDJq${dJX2&iq|J~wZ8ר]gȨzJlzXjڦJ p\Zzvꨵꩦ;Xhs:5zYLک"iJԊ*ֺ*s:ʭY*YgdQrXjvȢXzA*XpCM ۯbr {C`MhhK7MsկsޭpGiK}8?9RWuI71Kf_Osp?/}.?3=t9Ͽ dwwe%OBs&QD (r >UvšPj!Ƙ`_ hU-nb2 aA:U=PZG$b&2$V*dN嗼MI%fVfI{]r٥`隘Hw@Wޚj枡EXY )&|64ڞ(v:K-4vi&)J !Bu2$` 1@`AxХľןV ZM0@ `00?cFꔲa1KhV-|Zc2P.0m־^֫p%K79ġL|,,| 블 h|: p;v6,HYmH !h:ss{6ܐIO^<\V \m-Id}i n}ms.RRk&gޢY*Cm_kouņLy8BMjU^^i6l9CDwI{#"ZvngZ /=&ޘee#| ɖrOPExLpb»_C,k ѯ~?b (l~j/wNZ2"=ftЃaGu0"X./$ĭdT7صwk b7ēhJRJ!-i51[8Uъ]x‰jW1U4;DAd@2 ҐxEƄm @2p'Hc42g6 )"HJ>(*5HN14O(G9#.3-2)dhDɊ4R| җD_y58 0gB5ڬlZd0)8p^L9zr#F_''Oϱѩ#<'@(PSoB:ņ>TedwJOjҢEY d H)1PLAiJaO4%6HL;: 4 `:Tz?fPYھ씧Gi ƸԠQǩ:TXUfJ*X(Z(ҵ+l+U ckOתE}`IX^;bWڵxdz¦7Ab7+7Hz,J"Zuse$j!Y@ҵ]g:˴3ml9Vm'lw6խe:A=]t k\a.sg]ig7=-u5z9mXk7_z 7oz;XMga-]Mos+@hڇbt$LC& TU6OG; )2ELBHV4S4TeQ򍙼/? Z/!,\d22𿿿&l.q|qBCM)l $WKLW"㲳xs,,8܋((4߁33?CDNo..:ږqժablij=H*\ȰÇ#JHŋ3j$ 6Iɓ(S\ɲK= Ŕ^^)0Ο@ J(ǎin QL>JիAcʤYcTKٳ)@ׂRɊEKݻ*c@: [sK 9Nh#L`: dˠC@H)~ƺzװmbv,_ͻoic6:n+_:Uw6뇝7˳FcOm{o(h}_-!fNvT 6}m7t%X9ᆪAH`Zu`f](f$`qX!L&c4Њٌ6)QG<dU$ׄ!S>>Qv5@.&֒ȑPʴ wY*'tjٚWcyRgfbwU fHڧFD (r规(LnפNu 7*{Yi*ꪛ)b(ʑE+ ؍Ƃ{6lB{J^"xn˭߶j[bBaZ[/i&:pXRup g,BUɲ5v-Z24oNB )֬4n!<{̭N@A  \s 8$Vu+Wlp{ bU]O*8Y*sjܛ(Ѻ1$nqǚZB2os\g%JzջFFK߅6ƭoW]mg^x&}"D,z;`((\c2K 1`'Lr}J?`J/^!(*V_;=o̵7 a㿓+'M0g";}}5O%"o߱Tt#UO{īzyYm߀Q]WD`FȐfc%8CWv(!tNV(H]r'4z\.Gb]eX_5cf3~`F&ԑKV9[C>V<إ` Fg暿%w/rl qNNXtFէ^f^zBS袉 qҦhPh2j)^P MꠧjXBjhAn"h +IkO*lf̲k ٯHQ C-؆+ҁߴւ@ +j,Rn@ٻmRKǖ{z` -(\LF&o4 <xp0 @ PPA;2;Ŕ SG7E>om9C2,t y k*O֗ sckK`~BS0׬xʞ?"'|Y4# dHd@@AaU/|󠪮=T %穰k!^h!PY4A*`p ITض._U*:֥NxSѲugXs'`c=k e1 Zײ *k[kፖg[汓mq(ErutkTݨd]Qf "i-ob,]zwus .G=|+ך֮@K`~nv;7E( cj\Y?⯜4L$ 0H@!, )22xBCM$ KLWز,,8((433?CDN..:abl((\HÆ "\H1ËJLX!FrIR41J[| 8sܹΟ@{JPERM>5hMUy^UԮ^D9E2'eIKC} ܑtF,z4PٺO 4`/bg2Pq╓)(@E $Q 4 !,22BCM$ ز((4<>H33?CDN..: 78B?@K ,,8ablKLWDFQ"".$aÅJ<qŊ%b4h@Ƃ @1c(@@)Ueă1eNt"[Fxs͞ @ѥFm TZS>.J慫Y!"ړ؅Z$k٠Y[`ڢr&h9o߻x La >) A,V0 *bŪr Hh`!lխE54 !nim {ؚ!,22BCM$KLW굹 ز,,8 ((433?CDN..:󨩬abl0p@ 0 B hP`"ƅ0fD(pI%Wt2!M A&͝V@PPiTg,dNOt&nlHA*\ XAB3jB4P 8j\IBD$Y .2@i aN=\А0rCL"<T1`(_3|rȲ2l𚶠2˷^ ԵˑB^K'رWCJܡe˙ ^ύ!͠4bP[6/-1}ov0=+|8_'N#8a||z@C0fWe*~< /X/1v^g0B}" y~A-hB P=!,]c22𿿿.ql&|qBCMW"  KLW㛛),,8((4l33?CDN..:ے78Bablnvij=}}H*\ȰÇ#JHŋ3j(@` v1ɓ(S\ɲ˗GQ&͛8s6{J *ѣH*}Ht)¦NJJŰPfʵׯJN ٳhj$-ڷp.JRrP`L0EXl+cȘ3QϚC]A^:*≪{nMvOɰQsmo ;S8[^nسT=fyǫ.:8ᯟB4 _0`K fw]>h!rT]+qxᇄaIyz HoeS,"8%d7Uc@:yYcH3cHO&)R;:yPQNQ"^ZWn)YC eMafWK6`;1٤IruG!Jh]2*N">f*U|`Xj*t?u)Ui@ '0^_n&hD@ F A&ђfP&v`9 mE!dkxTjHnpY*Q>D-BئoDG;0/'5 DPj?'4 0,3 Lr7 n<2-4mɝ4P`H73 <OwMS'[i+5 Y0AA/vcKXL/dkԶoB͍EMzLt~s͆w۔3F^`YP-/y n\ꓶ~HPkK "̻{|+ςC8L2M+-Ұ?>V~/$V+_!]?B=SȱU24pP`7? .  6ЅS4Bs&D `.(A >؄a y`#PAXD)I SCH$X48& + b@2V%AI綸 "@ZP916t `7A!ta[&'iw}ǒMrw(cg?nutqg"?K}DO/CK~URO,R$S2dfJILhM(&+)6lD7#L=Sc:_Ή- lYx3IZg5`RTgI FH-yP5%Tt+PrB:ɜ(EQPdF-QF!F nc\KT !,]c22𿿿.ql&q|BCM̂"W $qlKLWز͛),,8~((4݉vޫzCDN33?֣..:ےablnij=H*\ȰÇ#JHŋ3" Q@C6ȓ(S\ɲ˗043̚8sɳώN)ѣHƤIRNJJզAׯ`'+װhӪ 4YoʝK؋q˷ߔc>+ÈCiNAT%Y^e] C"h'WiYUnZTxfwZ_C+@L +Eg]F -(00iH{ip ,`iv^nJP(ꪬfB%I=pFPFZnLβA҆wIJ9 ip)B^vWW ِCfhfX"wB BAP9*\曣;G;N+v-*2mʬλD@|o̍<.>OiB^0i)A@' h@5*QH EOF3$vxЃ ɤv2Hl B?d& `A p`J:І,i)c&LΒAef5Mi.q [%Mp.5J9͹5tVdg;y%SG@N|ϝ0J?y>v$觺I{*TuYPJR7*#z#]d$QykDJҒ~K(CX,0Қ4,"FS,PuӢB7)xԗ"u%J8Ҫ>4 !,]c22𿿿.ql&q|BCMl )$W"KLW֣ߪز,,8q((4z݉33?CDN..:ےablij=nH*\ȰÇ#JHŋ3j$(`` v1ɓ(S\ɲK#|1L8sɳgΛm(tѣH*](ӄNJJjZuׯ`^kUbӪ] +ESᲝKQ >k߿&J仔0È7ĊǰKmcf̹\7魠>KWe^.غgmظsLwۺ Gx@:_"o$!nN:}r,.W.|t˟vῧϿxGzh}蒂68{ K:haW^]eW)k!H'mh!TAKȑq 䑈8~ )59 啟Yi)e~y\@APp )'N!j  ~Bsʒx^t *$~~`lfeA)(BCncfA*kE[4A*P:A(@I@ Cf@]z,g- @(qR+n}XQJ`+@J0agnebk &<@I(W`g#Wg+XP#l@![)+|D0ưP"`<C=@72QϲA ROb=q/C0BVgc 4fP;B@-. г  `Pc{4髶 hs2^t~:9PA<;7Q˵t 0zyvc.?r -/ ٧ $ Qf_< ,& T쓕}~ &9 `."_( p $ؾƂNC4-ЃhJp&HlЅ0 v ްR1bCpzVp#"1;/bC$Xf`EJ%QBYElx:/@A u1sH%.αPuIE>$-n~L$$Q#m(yFⱇԛGscLyʼrLd+JrJjX˔=R e~BdsRqJ0 :h7Ӥf숉i~R&MMp P/!'!)J2qh:du>MۆO|I@?'uG@ r̄*R2B2t iEdQ F_A63! H?T;!"])I L1Z—v/:uO{ǁ2;)OIØQ !,]b22𿿿.q&l|qBCMl) $vKLW֣"W,,8q((4ܸnz݉33?CDN..:ablij=H*\ȰÇ#JHŋ3j @` v1ɓ(S\ɲK#Ks͛8sSb͞>=JѣHLp)ӧPJ*ӡөbʵW&^:ٳhVkٴp6[wZyKuFdž"ll2˘φ|2QϙC!hG^ 4[^SM{kmʮͻMsc; ' $:J'hfov6]i_=~{߀ H'Y|.A|ȝGIHq_"$Z#b,bubA+c4>@3c<u{aC%@`` tDF֭de4`$EQG[M7NIjY@o҉ən1$pg٦2_6FBzl ( ljE5 jkq䩼Jt" &j,i A>jR$4{AYZ y(G=5-3Xk,Hm/TT[{~ѻP($a3 |Bتpy5 O pBj{~q!`Bd$'F+$1&sL̮;sCD-5g]ttLGij M-VIZ` >`ԅ@mWp/-wOsLBwGWo6߈ 0^+ |Wm y X9@eQ A͢y$:,ntS;fs Q?*i;yD߭=lp6uSG[>8B.N5>O#@('@ #`@@9/'.\"cTT\:In ~n*)( ЇbpHrR}HC0r8D,P @XEFBibw Fk4]q*qsRIxǯ<{cyǟq'+VH)?ljXDRzL.QP'K:OV'7$)T R$aêJXFє8J-m'Md|/ `i)1drj M;Qf5i$M!rIMʢMpd9[rBCBss&o-dY 3P AOx%fAO.}P4n!,|22$BCMHp \Ȑ@‡ 2EP@+rȍ#/2420cʜIs$͛8mS'ϝ> *&E@R$0̨PhU&֬[#d2aZŎ=)Ya1.Z>p˺U X@ۃ|kV!,/-22x$!CBCM a%LV㵹<>H78B.1nų!D/^c37HA*\pJE3:xQG;~H%򡀕[FдaM?{ PFwTҌFF}p*ͩTuNtuϚWvMvرU<ٵX^Z-ҵv˷߉8 0XŇ?dʈ//a5_<<"=v}iL@o߹w#  P@A@ ȕ+XP6TFT!€!,$22z!C a$ KLW㵹ز,,8((4BCM33?<>HCDN..: 󨩬78Babl@D0a… .QbA^ؐcB=P Gdx Pr$@p@uT@P( ҤK-(@҇ ԹgT Z**CXJ q`Z 0x"4 0 .@h@"h0 y0 A<@4AB` !,#22BCM$ KLWز,,8((4 33?CDN..:  abl@@"4@C >ŋ3RȑƎ +~Xȑ 01d+[| EiĈΝ{Z, @1E*4тEXTO3H}x*ԯ.=jS:,kv9TUpt] P &,˗@ `@ 4hX <@4BB\!,]c22zl&.qq|W"Л)l wwn ef?$ nH*\ȰÇ#JHŋ3jȰG>Iɓ(S\)Ȑ,cʜI͛8 3ϟ@ rǞD*]ʴO:Jի nׯ`sj5ٳh^ڷpeѭܻx:{Ѯ޿kѯÈs$\Ѱǐ#/dLѱ˘Sh9y7K 鳢#>ͺuWM{(쇲k=dn3oh<3lRzvۻ?=awGx>{Ot||`j[ H&ހ 6FhVf(Kv!Ox؁u&("3h#N0 #)(Aʡ3T%XI &@W%\ +, $薠 (k]@ b $)Z U, -.JxjkAg$ Bgi*HY:40HEhDa"8@Ѐ9DȠ A2L3D1 ν63@bՏ@ t I P@!,"]mc22l.q&|qBCMW" $)KLWlز,,8s((433?CDNwwuϾ۽n..:݇{ef?ݴעqզrzabln݉H*\ȰÇ#JHŋ3jH@` CIɓ(S&ʗ0cʜI&H.mɳϟqJѣ7="]ʴӢBJJȨVjʕ ֮`Êtٳhe~M˶ۋkʝK`ܺx󦽫߭| 40ÈN̸ڲ#Kxp!cbΠC{,iOykƭ_{mo랛{o |qŏ+|s͟K?}uկkߙ}ݿW~ϫOr{ũ~}G9'`H 8``-Ƞob!pU!EfX!ul#hJ+(^l.ʆXb};wckCy"G#CF6$ E)%O""x`\yܐ1j$Z,p 7`pIYPmP r*O܉zB, ¡hk#(:݈K Pu eꛥ,6-,jzc|6 [v溛EDaJl,BA-qoٶY|;[4RD(zBD@P*䫯ԯF,Z0^,ŷem,|o,p$ln+/5tQAȩA.j8)q> g=9Eг -@G>M ղud1Y`Kwt`g h =+ ,x=s˩ -99=8E$@⊫vE}<@p}y秱wm_`! QB3 CN&; ?FS C_|i'2? (L@ˋs@ {T[>0I@ƀ 4`V pNO~[Hf5@l D`&hY  .B<Pa$ Y(FP!4 uH'V>>@@H;q3QI(!Lo!,#]ib22𿿿l.q&BCM|q)l $KLW֣زq,,8sz((4݉wwCDN33?Wؔ"۹n..:ےfg>~nablܸH*\ȰÇ#JHŋ3f$ pƏ CIɓ(!r1˗0cʜIʖ5sɳω7 JQA*]ʴiѤNJJiǪXjz*ׯ`>*ٳ2]˶Enʝk.ݻx˷ֽ~ f x> #^8Ɛ#|,劔/k޼03ϠxMҨNְMڸNȣO\Ї>Ni^M~O>1O(sO_ ?G_ֆFV!mR$ᄬ1Ebo%>Љ(a \p`A-" `Ff=*2dHVyU4ۋ@BJ8`F&%o\%BQ0 &cpfn08@y n9LY[u 駕9J@fB (蠐vz jꩦizgv Z*drj`kYyӯ0,Uɾ*ڧ:l-R+2+Jy㲤ج I@k&K JB/pB.B t0d0o +BP\ArAi)Lr2n.+D3YsA)LA'D cMPG4tn=u $T} h5m]Wa'0`@V-O۱5O]-0'1 6EmA :y~83uh `뱹-@n{,  w/N0 |ju͐P/A-7%=xs,%Pu2 5ARP@Ѐq_D(=}S @Y 'X2YI5Є( %X 0IZ33: 2" X lHMG(F6AY"ūhɢ!,#]eb22.ql&օBCM)l $KLW֣g,,8Tqzrx݉((433?CDNnww..: ے3fg>ablnH*\ȰÇ#JHŋ3V$00Ə CIɓ(r1˗0cʜIJ-kɳϟoJѣH- Mʴӧ@BJURjʵk֮`Ê5uٳh]M˶ۍʝKWںx󢽫_| 40È{N̸Ŏ#K y+c9ΠAMҨNְMڸNȡO<Љ>Ng^]mnO襟O𵿏O;4^ H ~HP`|Gp9` C@ d o9AbhC8 0 ͦBDBk  I4:P7>I$/J  N~el2ii?8BOFÙ_Fc| (ANAg89Z}6 Dd "zgy6' zP&Tjj *)-:*Z b?z c 쥋ZI0,":@UƧ -+b-߂ {Bךxnz@K*j 7̰ $YxkAo C,iq0IK3r 0\o@; -#0 OP;G03h`@1-r ; o /8 IHMulV[ˌD0# kvmt4\ 4x_MxCHpT@ @ -C#[.@7pznCĵ.B篳fգ7d P0u1PBF@D?dzA ͧ;f&4!vP=p  23PR0$١ !>  dbD)f) !,#]eb22̿.q&lꅅBCM̛)xsl $zKLW,,8((433?CDNww'Ͼn M5..:{ef?݇עqզrzn݉rxablH*\ȰÇ#JHŋ3V0Ə CIɓ(r1˗0cʜIJ-kɳϟoJѣH- Mʴӧ@BJURjʵk֮`Ê5uٳh]M˶ۍʝKWںx󢽫_| 40È{N̸Ŏ#K y+c9ΠAMҨNְMڸNȡO<Љ>Ng^]mnO襟O𵿏O;4^ H ~H% o+1`>@fCEHm"x!C(c,f"D8cԇ #jBH$l% #lp\\Lr󩘀 H)b H `&Nh  +Y'kw0g!0 qh9О) 9LJ)CpxjʩA 4A\ébP:jB kA  :+ATݩ5*hAmJ-G+.A"qnˮ,+Kk9+?J>% < 0+ C̠T\10 8б6r@[2+/0O*jL;<)0s@tPE0/ *ԓJٮd5E>-_G jdS ul= ]vHSdj;sN}<iE" 9*cG;N F/`@Ynh@E)x K@ `@!0p{g뮺F 4@ T_qt"E %`/4!o~ U$fǵH\ A' 1i$hh[-vp)aKB#,l!s^xB#0 6F1;} XAdkD.Q8"$JsE j17Kb@!,#]gb22x𿿿.q$BCM&z KLWز,,8((433?CDN..:ablH*\ȰÇ#JHŋ3f@@Ǐ CIɓ9\ɲ˗0cT)͛8sHsϟ@ sѣH,ӧPvJիbʵҩ^ÊR+ٳhV4۷KWܺxR_| 30ÈYN̸1Ŏ#Ky +c޼Y3ϒ=X4ӂM^W5p]ÞV6vޭ6U}T8G#_T99C.S:-cnR;!Q7ͣ__]=ß]>_T7&~ 6x6EFځUhwn]6aJ ),htµ(/2ی8Xim9ȐlA 9$kI*$XQTfAf 8e[rg_^鞘dyfk6{|9uY}j+@#jz"0>*) in)j 3@9`ꌭ j؊kvZ~쭉!'(lβEYVk>{QZNd˙J骛#mK  k"Mj"̢ @pdE<1\Ǝu-H+V @ `W (0AL`3{,0}Htԩ VQ \`@WU@,;-mԮyt@D 4@Xc(@,]\`pS]~.!,y 22BCM$KLW㲳,,8((433?CDN..: abl~@ pa |ĄT8ƈ;z8QHMz#"|ya̗̜PPO:MJ``) @ PPG $# dT #ͨl!,g<722zBCM$ KLWز,,8((433?CDN..:ablHA*\ȰÄ#J|HC Z1G )z$ÑMDrJ-#̒5mIqO@3ѡD婴қJ>TQZ 2kR\+ 6òdn,+U؄Hnu+TϹt6oϴwj`ws䫸[#KL9/˘-cY3͞?_-z2ҝN Nٴ/,y w/<̛?Gx!z3X׍퍻D#d@z /\Lx `_:~ ` }qDԷ m !,22z$BCMa!C KLWز,,8((433?CDN..:abl@*Å J4q@+0QbE8vLp!G(HXL S̗?B$P@?4 OBO&UH ԟ2pH6M Y`j%p]%Pm 8`/RJ @rVJAO`@/Z*`eJgxa_@3/ zQ ڵQ !,g,322z"BCM KLWز,,8((4<>H33?CDN..: 78BablHA*\J"ZܘF3~Ȉ%)*@aҩ"VvUcm׵H遭ΥH`U\!\L D 8B !&^ͺ{lƝ{7ܽY.7e+]Z9l捝?>:֧f]rء\|7Ǯx廿:=!,q 22BCM KLW㲳,,8((433?<>HCDN..: 78Bablr)d@A<@…>@"E/f1a$$@PK x8 1 `QX ` "d!F 4a qZ!,#]ic22zl&.qq|W")ln~~ij=H*\ȰÇ#JHŋ =Iɓ(SI+cʜI͛[ϟ@2УH*]jhILJJդSPjʵk#zKlP"Ś]˶IպK.Gp3ʵ˷/߼L, +^<Č#K>{˘:9%7S i'>ͺWO+n{ ;%x<. .otVNl˫za_>ֿ_s'V%߀d@"t`GFhaO 0A^H$r"A#"KU0բ^/hS;xz%!p@9$F6ZI _0tA d_PFd`:P $wQA9I^.褗.ԙi7rnyw zӎTWvB\^dͭnw7a'݆6o:O7HǿO5`?(pjFI} ) j0A|sAp@ BPy4TH~znh<~<C$ʅC4,$qSj]P*'Db]`E 1_ET$ᦐ8Vb:&Dpqo#.ẉ35q- yBjюdюlK@!,e](b22𿿿&l.q|qBCMW ݛ) "KLWlز,,8((433?CDNn..:78Buufg>ablvېnH*\ȰÇ#JHCXtƏ CII5q˗0cʜ)0%̖4ss͗8{ Jhğ.]ʴiO+:JH'ZʵB&zKv+ؒb˪];@³$ӲKw&ܑrKȼ| 7$xӣǐPq˗ l?8>}ֿ5kh[' T@h @&oiDqbakv!0&zQ*-"1rh5ڗR*ɸ=g Vcd:F@T ,P%Lԓ96Vb%]A*)%Cde\XO YtptIYy}ifz6$WH=$1hgUh:@<%WШ' k «>٨&*k: Ukd-[e ZB" yAҎm yh.-?i_- .Ђ}],78?Rj,&=@@A &,;Mfp /A 8  MA=g %aU- 9!@p@@B" TAg3HA -N"(.EGd#ܸ'Q7cvGlnablH*\ȰÇ#JH"BXƏ CIɂj5N>4uYb @zSo'IՇx7D_S >`}EHkvxAfHzh@6=x"xL(b{bUhx#]8#H>`YBb ^*ɨ~t=(Yye|%9y%z)Вe6y&& wi" ygu% 's 'zp*(C>[J#nZ i ,Pjjn⪦*JګNhzjkg*^ T찷{,fFlnŮ:eZ+ u[+c+j@8@!p er'Zx$0 K{XI9$ 3 JKq'*kri$gv$33/ 7jϥj1Ͱ@$ :C[m#h]˨dWm6Hr6No} Ppҭax pxp*=8-`n3Ak}zG מ렟{Aێp{ .{O@Azb-ϼTLs=q3F T8O} 7~>[S>@P@>moۊ>yA^ȷ0$-l 0dZY`K?$t`B(Bd _hXA,! , P 1dPD#NE*N X`3d`1LV  B`"@FȕDЀ4:9K j"GSZlD \ F@nH#dB i"8%'IR n<J \eR- @e+[JIn' ^jf>H^ P:[|)oŚ&,i셓Hd;Ώ脜` :)|>.f)P?&] T) CЇ"܉:UZ(Ff = FR F9)hKҕrz(~@4Ȁ!,g],c22𿿿&l.qq|BCMW" $KLWز),,8((4wwCDN33?n..:݇ef?{~զעqrabl݉zlړnH*\ȰÇ#JH"AX80F CII5|q˗0c\rf–6se͞?Jы<ʴS?.}JՈQ{Nʵ׬8ѳˌJ뽵#u+_O&ճ{#?ow߀wr}G~6P_%XVXz HZ!z'RVbr(`, t"A pJD"xJ9B#c IzFt߆,dM:#~ )9vUZ $I%]zdCZ ]=PjfWBq6'_٦y2i`Hf%ygn_w ivD韗(1:iieJo)xjYd~^h֩* 0$,Q0 -DV,joC׊K҅n[nk͛ 6l`oW^M tAX _˂ 9s2\ 0 $< ɶ rLA׆3:@!d+tW/git`949T=SK2 =3 .-G#N  VU}*fi 8Bp@xU7B Ѹ4SUM|4A \9 AO^+>ʰgs%.G d3 R G Df_s޳ %M#> BFhg@ @dz KZQOYHgl ,E[0 @Bq&8.®٬.܁FÞF8@@g808##[ TDJh+ފ|9 ~g[ 1Q:tRȐA#H F ![A@3J(|HRd5ddNG(Q!@,pģ r4d|@ YryLO\" X0Qy˙pr&lH,0%B1˓lœlHJLs;Ռ_x). @fLi%WS~8NsAC!g=vfs5 odQ(m \j>gDOa&A ~t<!,g],c22𿿿&l.q|qBCM"W $ݛ)KLWl֣,,8sqz݉n((433?wwCDNnu۹..:fg>ablܸH*\ȰÇ#JH"X8PF CII5|q˗0c\rf–6se͞?Jы<ʴS?.}JՈQ{Nʵ׬\dih}zA!x. Hߨf*BiJ+ ګzaA<X{,r tm Ųږh^NB |.!HA.].4p $0\.@@ UBw '1ht@d-C1LS ! 32`83j0]MLq͌ :'3Zt!3BFӠmOs%M#k X+=tZwMm14A>U_7A2ǰTU}{ SxAݦM8ol@E#@89\O.[]NtGsZMi\3B7O-#3챓\` tpB \o/-=!xLQRޟ|Ѓ%v؟^PK <i%}H? *F>( t Q X%N k@ mNxp($LU,`L VP`$0R(9lGN8p 1_|s)m!ƻb` 7(R F=BbըE7f.@ \P:e 5oolѐ;RB2q !`2w$OB` SDA WBQ-sK貂{%/]9LS{If:3. ,LfZ3&τ&o$&9FFD6cxӎ=L=yO3@"~s'.  APT3,eC+zЋ&rablnH*\ȰÇ#JHXƏ CIIjn@* 0pkke,(,Xz%s1n^kj{z @֚{ ꮻWE" k/h_oq$#Euvs ,rPhc# 0,'Aܱ1l5u3ϒ(-#s ?]CG'2<Jԩe\?j2pUP4\w-m*te-PKT@.n$8C /X~0ոB+!BB*!kX3 Dj; iyD/@?<̋:XS>Z8@@AJWb &4Pdـ`9R[rdD%@tɁjvU v^((u֙C^>$AΉx" Y2C8 A*WB$јj*~dZf )Q \)`J"B[ $4&K@nT IVx-ApXn2t.Aٴūf.,$ 嫯^U/ϚD争f1\1][f'Ag>(N 2W0A!p<{PG駄@cI+MQGht^uZK_Pd uM< @p3HYYjtg 0xY`iL tfP cUniNBTZi7 B@@Xbe>0 +Dm-> xT`Fn9>@Ӈo#e~`f E.hi!S? oK B$ RÑp!~%a/%B$qK4bD(k$58)qWbF..h{aWEUykYr8qisxG99bwF@3;k2LP8 0$Y@^R!$HPb/ Q'MJƌ $+iJy9f(yT\( !,g]%b22l&.qq|BCMl )$"WKLWߪ,,8q((4݉z33?CDN֣..:ےablnij=H*\ȰÇ#JH"X4Ə CIɅj˗0cʜ0![ɳϟm R'УH*(GKJzHTjJ*KBKל`˪]6-AKW٢nӼ| v7.o,lQǐfz8Wtc9{-DѤS5-kYGt XiMCݼ۝5R};N ;:CԳ +ag4j)0`in cv(YB"bC}@kAiGrKFѲo*mDBCe,:k.~6ОNy (yb/ #&)FkP G_@9Z"t' <"B$;%+L@- s̃ '<9 )88Ltѓ14b,@< W *`M +c 6-&x(B7_wtK4 ,B\BU"Xxܜ{sgp㞺^~] p p );]!A,<# g[[e@ؓ܁C)Iu^< hpxT`ɏ-'a$Pd7 z7 ?1r6B@~i(B$C()f$ qد H!&T '0M܊{XǬ094PEEE|"DVF4FDGcX5J8H q|<AGR]vH12Cv,Yd+HFD$IBQdn8IPꋓO!e&M>TEdegX1@j-oYGWh(`19\vd /hFӅ\XGCל3bM8 81RNXLgU)2v걔 =vSGOu4se@!,g]!c22l&.qq|BCM)l $WqKLW♔"ߪ,,8((4݉z33?CDN֣..:ےablnij=H*\ȰÇ#JHPԈѢǏ CIǎ5Nh˗0cʬRJ,gɳχ5܌ѣH :bQPJ ƪTj*J\ÊK+NdӪ];,Ql)ܹx޿cu7!7,c2~L '[LB͜CzI#4u\YMAٴsocy xR)}_.Ṃ#u.=ݤva &P0+$ʼn!DC&~A8 C), &C=P HHa2ax t( B,bv߈ܕ(0"5Uy8<`)EҐ JBE$0L@ZpetP/c2W&+bAQro#"ygq6tB#A9(p%(jb>tکu8!䇁b9Uyia~@L ƫP~ P2"l˺ʡ7Щtz-YjF8* ;nkٺ@;/u. 0`~ot:D@&@GAk T[S}'P7ЫnЩ4li5L/w50 ,J:/s-=4.PBJft]ݐES @fuhluMK@?+/ ֮]YT7$ I -)@ KF[6nm?Fz]sy.n譏.>GȺs7 ;M)_m>뾻([ ޽$~oմuᗭ\YdvsU>%X? 04Apic. `РAnf0"lKJhp ) M k)!ɵL0 NdbsD(҆7Ä0\8.&b$#4O.dcF8ΌgL"Grv8DeaD#(G QIIK'IO;_Hir|$VYRr7 buҖK.{KQ2PLTܘIAҒ fTLhR\Ԧk!,gy22$BCMHP \p2d8aċ bČl(S\#˗0]œ#͛6o̩%Ϟ+DP=z "a`$8#īXB\xׄY5!XiNdKjĴ ͺHm+ sݛ`@!,g~UW22x$BCM9:D KLW㲳,,8((4!33?CDN..:ҭablHA*\ȰÇH1ŋ1RH0Ǐ9IdCM4rʗ[VIӢ̉5sJYPυ<{QBF*ҥP6u:5jԪOZJUUHz)6Xb=rmV9& .͹Oڭin׽d] ٮz xƐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻߧ MpB㭝??Z֯ή=8C нO34ʏ>PAɑ@j~} ‚7@(@ j@,i p%PP@!,22BCM$ ,,8㲳KLW((4CDN..:ҭABMabl9:D! @ aB J@`ĉ+Zdƍ$#n@RbpX)E6Iœ) $xN0xGH]4Fp$BN`-@A$0 nU+ ,0*B  I0 0Dȩđ,vr߯G+O>xڔ!,22BCM$ ,,8ABM((433?CDN..: ablKLW!@aB 6@0D+2 FZȉ%N>  :hQ:kr;زo:bk!,g\e22zٿl&.qBCM̛)l KLWqز,,8((4ww33?CDNϾnݲ{..:݇ef?ێ~עqզablvzr݉H*\ȰÇ#JHQb3jȱǏ Cz<ȈL\ɲ˗I^0%͛8sr'> $3%ʴӎ$ իXF3gլ`:`ϯbӪݙٮk=iԦYhݒo Ysfu*YNy?k^~G=wO_jw_?{hJ6X{fğnaERhb]8Qz!JbT"QUh8w͹aO9)䎿E曌(6y>.HT:i]qH2$CZ܉mɥ^\N9fe&mvfh"$5 ɧuAEkB>*Xߤɩ@Tiͧi8ꏩ^7تu6a`믥ʫpΘi1B{^akmZpڪa}^5[N쩛Ϲ2Eֻֽ櫯P[D !\d \B BRS(  ;" =A8 -WvBD BIP4]4HStS7N QW-dmvN~ꂝmw kv,w0mvz5~܁ N [8ODv2TNi4 Cz 8P 0 ؞s3.TtOsgNB78Go<TPٻ/?lP'K\!D@;TK8P⯀8àRLgCY x >ARBMAX@ A>T Dd(P'$x .0\ Az¡U$ xъW_H 0 F0%%c4_CȀ@v4!8(#B舀 ##P4"22 AF0i#$3 TWH!LHp2X !,]c22𿿿&l.qBCM̛)lq $KLW|ز,,8((433?CDNwwߪϾn..:݇ێ{ef?sqעզ~ablrzޯ݉H*\ȰÇ#J(P@ qƂ3Iɓ&-QJ-DI͛8a^)1&OJQIdѧPT%GAjʵNzڵٳTJQ¦kʝT-ۉnK_ԮX+&nļ0.Lac![Bbk0ϨZh2aNMۯ]tsovڴü+7G5GC'(oubwë:;'}}|'H@q7g6R\=Y =e )_}P{iiayih\l"HZHF+zx"q yX"8YJ19)ȩ$KH␠"a V)dNɗ]z#aJ")"eeo6N-tJgxZ$m H%而8'~F2(B蝜6詥!i]Xb騢jzdj뭳+luz鏼Vh%cR gʐ+U%̊묵&lyl~.˭.emCOVn&/_”-\p֖ 1p 3e<W&'z r",l'L[{dѷb0oZ|g9s>,$O`k4]At6? FMl_Q75ђ*tC`Ap_6yeewA8]hF7+D~Ga}s-%/8@)PNfxa! I擑d ypݟR` a7o?%_~e<<98=>gnaC:m~~WiIWg<-y )> Jx&H@ @ !*c'+XJ;)B N 'T^aAڿÃ\><8pT0`&P!Ax@*"n:;=` Q4PF3zɓ ?wGH&390 G'፥4h܅d7;z\2R,CdFpc o'>>B5Ba.`/ B d /pisbiZ5wX0(8ZuTBF qnTHEZQ\kɨQ`b`RD)TAӣ0m &STjA ՚I%S2T@ ?pMZ %`UnA]o:0)N\JׂqWp|֒U(:vհrMOBv` rYCUW^H,HvY@\PU> 㼦BkamKE=K)`/ ln;VC#m}% !,]c22𿿿&l.qBCM̛)lq $KLW|֣㲳q,,8݉((4zwwCDN33?ߪn..:ޭ~fg>snېablH*\ȰÇ#J(@+PcACIɓ+Qʂ-DI͛8 s.s JÝ{&jѨӧPo"qAQjjkCrKTUZ[M=ȔۻxEؼ `Wa_+^PaseL2L…ʴ:ٲgƎS9xCk:eb԰ݪ,yfұs}au "m׸viq㬓ז޼zH{m34 7rӛ}_/}頷m/JyUNT8]\ʡWtme$aUӁLu(bmX_aK0:(!tqvx!DӑA8dN6E1 ~TwQHAiuI`%b [ji&S`lY%H?B)gɹS)ܗ}Bihz{ '>]i&8(|Zj^Hd}iu.RLtrzj՚ 묽'pJ*h,*bƊA{춹1+z-v:F#Ӻ+{ko^>{/|y| OCXlP CpE P@(Ltl 䘼)r-LyoHEۏ;oBRL701g V_-k]F t-O`xpuj-pA7~=7NpI\۝hi"(@HW8FJqy&E.y%İ:l%Gپ)~7B'?sablnېH*\ȰÇ#J(@^<Ɖ CIɄ?>h!K*OʜIM)1zti0͟@ ]N=*]fQ,} |ٲիXVժgAYÊtQT]V]~mKͲz=k_x8-<0-ǐ f8mȘO&x̠nNШF԰Ǯޫظ֦uKg6?I9nղ.-8%NoGj>ܻn'_:]oԽS^'{q_{ (x-`q)(kYwބ0UQa„"gb~|"iG P]sأk$o1hu$y"jE)8'#U0ٕ` @J9&n>zhјvis>S饓}BZ 病whngVgYFJUprSG䅦F* *)VV#fZjj Pl`XA^"Cx@ .D`UgA 6I@:oI\mr E1 Mkбƹ@80>`@MVv(<, rZg)/lېnablH*\ȰÇ#J@Ċ 8Ǐ CD 5r$ɲ˗08ʘ8sCoJKWhӧ4zP(ԫX"I5(Ӭ`ݚkAbӪeIYhʝ{mٌ_]q _+[ċ#mq^ɘ'vws<)阢>ͺiꁅ!}δs| x.y# H/_Ḷ IhK{/ꫲo]#鏷?j~7J@Qd^g %'9@>7!9hrx]Ee݆"JFnab 8[0c_!]b>gxb䭴[.2iڇ(! !gDJyJ=Z%\^]a)gfk5%oFƢOlVBu&dzg j؞YCHXAJԝThheg@4`k]I m@ 0 fCl@D ӌH)B<`CD QаBvmB ,[Ѿ5mppø8m%mtt@MtAk"t@A!Hּu 'l )דt K@qH1`BP~LRr! !-|e1;17#T:Svel@pތ'tܟu` Ñ6Y>7'Dޜ7n.~d:$Թ%!;D*O69~zPB)Q9^J"PPb 0izQb䄈hP*nXg *!4`BF2Fq]'ĕP8zcB0Dzюz[;F6.':DX׺C"9XdiƕEk4*9$F& ,Y'e'4"@h \dEi*;e ~Vh\標WgFAf0p @ Wn=RP@ h@9p)4H L A MtdBHO>`H5LfZ#9[F9Ql.(Eb +I&$LizR!$2 rZj*` "Ə CIRF'rX˗0c&L•2sӡ͉?gvIQA#&=ӧP79ШXf]*uU`곪¦bӪuIVYoʝ -Cfܺ Kpe"68!VڸǑlǐ?J4T̢5lS)fvOءe۶oq8J  6ݤK9`ӳn(tӟ˜>ѫ_̓)uqewR~W |*`x5uw 8aكn)!4#W}aVe((M)"Bxrfi?`/^P32դ%5R\RWf4ud}C%-V&cj)o՚=)ݝrɦ`}n'v"e<him]褉FEbj2U襞RYZjͨ*jFrjZcJd|;t"KvBTkFHI@Z2%!BA,|`mN[PtBtpAd;P*,* pI 0PDX|InhNxP0Ւ G{_(2O,s -p͙դ@Q5#ZMIZP ] J/׬@H|Q)G>ʆ 9Q~fi tx$ \yiCN '>p5C D^AJT~@P Թd j쥃:#@ T'Fյ]A40BP43jRH,\  ! nBپѯ 0A  j땹M "ߜG= !@ p DP5h&vwjXVhA0RI y ``Edl5C o&!"I)_l!!jQ$\@  ~ ]8rƏR@ VdOPG >reS !Q pFЃGхgK00{c$$YQ .<;PbQ7RW %.)^R0bL$Lf:Ӛd4˃۸=1+Y'ˠCj};^]fê &fM@ {[!_? h'[L稑K|ܱi'Gߝ['?4m^vyOyQ9sAsȟqf͗`i WQ~fFR9Bhؽנ$J`q*Ȣ#%Hl8;7wX*"LH䓷[.Y~Nb!{W񅩜z8hZ҉md$#Z%xY($ {w©SJ:@щwnJ1b(\y_B'o)koZ T]*  k"Gl&kFwbK/QndjB'oyqZWX?㎻A@]`C=i&A 4 Hx3} QAl)X@D!  +Tk!;v@ ?8`3ܐO0#= = *E -U1:Β%Cb?XtQ+_DJ8%ᬤ<({GxY9MmP" =Q coANκrEZW$KzԚ(M%A"?8<ٶ(V:J_ 8֬۷! a]x*\mKpi'>ذmp1f<3D:#eШ=,PXөc㕬]۲sLѳ_ܺ%]QѺ 1.W%8:;Nگ{Rӓ/9AKLݡod}grY'6ן6H'l1HaXy'(RI_Y x"޽a, 9)>Bh"$٨R'e"C8X"IrOvae+ricRak"ihp) uکy@g|g^m5s~b}*([B4%٧;RjULI֩ݙ7*Y)zJک>g)6ZLsZp/Z+~#p,RR8,y|ڗ鴘%b;_n $xЄIP$x+[i۞DX0BX@2SG.< pWWlC ?,J/;1C)8 Qo<Ĉ .)l 2+@ %'6tξ4ϫ*4G|TS[$WsFx4ӻ&b8xTEUbTx$DebD,T3B \EmQsO0@%h{g'CQ48AxvzE@tG$ 49AhpyN8xC, Q}e3'^.#M Mw| @ 1 Koip=CCw3ԁo$ [!@<0z03Lz% BxA8[``u9DT"/Ѕ"\iJB.dX\paKȿl(@`" CbIA`@bcA C@pD, E|cB.Vqc2 'p H!,"22x$BCMH@*4Ȱa!:b‰ -jƍRɓ(S$@˗$2&͛6o̩%Ϟ) (Z4rKtTDFjZ5"Fvر;n<5p;Wu#kX!,=22BCM$ KLWز,,8((49:D33?CDN abl!##/H*\ȰÇ ŋ3j8@ō CI2CH\ɲ%=I&D1?&Μ= UУH-5SKLjը,P(Vv+Ȫ9@LpӮg@X@o]w 08;د@Pa1Ð5: 49$tC H^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ_*무j뭸@` k D,p*~. Aڗ-ĭ U[_+.}{ܪ.+K_@!,6H22BCM$>?I #GHS&&2))5++700??~'虁'Sry Sv@ޅ#VY@!,rZJ22BCM$̂ KLWز,,8((433?CDN..:ablH*\Ȑ@#JH+j!;I#Ȍ$S,hʗ*[I̙5sJSτ<+I O]z@K6`@Ԣ-U& L-Ѐj[`H`aY>X^MQ580ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸smk |pc'/;9rv.Pjȱ\i"ҫ_5߫?φ?!,WP22BCM$ 㲳,,8KLW((433?ȳCDN..:ҪablABM H  *\p #2ċ)Vƍ%~aA% 2(t %kЀNKHX } )@N%j ^N`#DjC tx˷߿ LÈ+^̸ǐ#KL)y͚? 3rMV=5_dvH۝a+=쎦=wQxhk};qI? :r!,9122>?I# GHS&&2$))5BCM00P #XH`pʝKݻx]`JXpa#&Xa><a忒bNXrΔ5,LS9F 3k1N긶i !,-/#22kv>?I#< GHS㨩&&2))5BCM++700G:f==Ff:̐:fBCM$::f::f::f:GJ8f::۶fffff:MMPff:>?I::fKKO:::::ffېf[[[??FOOQfggg`N: :ڼfTTU:fGHS:fHHLffLLM:ffdd[ڏېfee```ېffې:aaaeڏ:000_[c0g0kkkCCI-]-<B A}@kQ'PO!t}u6VS6%hBaڨD Ċ#D ]j԰UŁrk1*x4D`a%Ef&(RCOb:!@Q<&gx|4@ԗ_!QAŜƠhi,Y=dS"\FՋ0btMmPUgD\ @]@R $ Zks*&Fjk!z Dm&f6ҷnh WuJj{{1u=!立`RKKNУ024YTzja}𪺌.tƏ;!i @g6sb@jcBݺ//|߃WogQ圜_6eQ#Z᏿jN^L@[`B2N<R2d5db*Єשm(287 Q(C@|F0GP )0 "#Ԟwf\/ӹCM7&&: 銥BX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Ƀ(A (GIRL*WV򕰌,gIZ̥.w^TB'xaH2f:1XiZ̦6Li^S8rc8N&f;׸yS3y3ֳ>L~t8BGІt_D':"(2nYtC!Ғ%>Cj&J0K2S)*ZRu )kVx%+MSzԤ5ٻ+`[Jk9JXMam؜s`3׶FDd,gŀњ#*A މҬmER՚ls[~m{Im2M"7[}tSSjӹSt{\V׶.wԆNY.x)^ Ewj]g7~;_B̼~綿bE)`Gp2kQۃ480 L4 p4,:A AH1>l 7}0FgЀ-,ĸq4B1%W+3$NK L :BK\˿2Nj$G9ę,HVZbw 6v.khy)Q4IDҖvo3=Ls4H<-熺!5{Uk6$bU:uElk53a?Dnld3~vB-F6zmDގj p7u.m{1m7 ʻwI-|뻣~р3'>3|O~Ј{Ňs ^|,Oq,o7_Nܚ40o yS}.t=%:pTHoK K}Яj\D0]dѴ|l[t(}v >/|:O+^3;֙yZ><߱~/~l_qʾ޴7oޚ_7~o?|3 }MJ??~ަ/ m,?ϏIo$/3{Hl g4 X XIPuSWHHtKׁHhl"8~Tv(G*l,؂z2G4k6xvHk4yFHHj@pԄPFRjTXltZF\(j}dX )ln3rtX=xx(Xw {G"ЀGxGȁGGt^[EHG(tFsP4\-eN6$'&d({@.'0c29wR2v8:b<ٓ%fBDD)bCpJLCNaP;46V=X`KW`7]i`A9Qvhj_lٖrt_vy|i~i^_39]qј)]Ymqϕ\)ǥ2I\&՚I[9"Qu )[ٛZye XWy-iZY|rک'Y4uI/)Y˹ў YY sNxŸSwX(vBv*j JaSYZXgs*t  1KW&(t*ѢE02*t4Zq?:j77D*F*sH-פNPrR: 1hSwX i\ڥb_iaZqơjljii6tjpt: }:zhxhJhZh*zgڥJgf:fꤟeeJdd dZc *c;p X7y: :uz Nκt1QglVk[*`')A^~!#%ԩt n 13>5~79;>=~?ACνEζ^^.M.t)\Sd N'_KZ^htőEg[KBgV?Q^~GNξNοSSΔR~nR.^Rޓ.R-KM>+Sn"$&.] n24~~ ,"L&l*. ,dXMAlg\n ܮ.o!#O%')+-?/I.>QWN.Nn׮t579ɮ,LlĬ<> ,L83. %)Ie$  %IeV;|[L~NZ^`_/Oo䵏䷯OvPOϐOoO/?OߏNяN?ןN?Nݟ/NOMM鿍oM/o[]_@@ DPB >QD-^ĘQF=~ @H%MDRʇ+XSL5męs@N=}2PEETK>eTU^ŚfS]~8Xe͞ZVŶW\jkm^}i`FXb_?lpodʕ-|Y3ɛ=f4shg;FZuѫ]}[li.zn޷m~Swp=6\͝?]t_Ǟ]vݫ{^ɟGzɛw_>v(W!,   !!++1122222222222222221222PH߼sd߀ͯĸĩvYB>J~}Zr{Ummwug{egoADj^Z}gWeV|bSzZJqVQkDMf0Oo:?cKdD*N<:<<=:=>=??????@@?@@?`R5[L;B;K47P22O11P00P00P11N//M+.I)0H'2I(2H(1G(1G'0F'0F&.E&-D.2<243011..2')7)B'E$E!!A!!@!!?!!< ; : 971011111000///.../8JT N @ 3 1 0 / / / / / //0,!!  !!""$%&' ' ( ) * + , , -, * ) ( '%#!       *<R ` jUB,'H*\ȰÇ#J l|C@,~qȊ#IfLɲ˗0cʜI͛8sɳϟ@ JѣH?L\ʴӧPJ㍑(1j yqBUPglJIӪ]˶۷pʝKԻx;1ց~ZFuC_6[l,亘3k̹ϠMiS^ͺװ;M6ظsͻﺶ nȓ+_祙KN;kͽyӫ_Ͼ}tO?߀h& F(P VXۄfJYat(}h"_$ޝ],(hcS9ݨ)TViCNt"bUa5e97PR[ p5oxY,IRBgiPLiaj (68T A^)Kf)Pf(r:G f:{::I_"uةE* ʗUz:Khjj- e^?x$mDADQ+Nd +.Q[QK oHG,Wlgwss&E4$.)If !>1 9*jJU?@^.=zezԈ%U=Q!?hYoM5AzV\}U,cF 2 JWҖԇ!JCĴ07ALVD!IJe*S-LiPt!KSZO3Tq `/XJֲhMZֶp\Zփy-dϓ21hy$ӱXFu63ߖsͱk,JٔmCC+ R :bvb(P/[+ZajB*+c"WYZjuZdU8]bp{E an;[4eU]]Fz xK_EkC 2|Kߵʴͯ~k Y1@8'L [ΰ7{XGL(NW A[`dE5/LdHN&;@Vi hR$d{J/1hNk6ϕR/oy&]6Kļg2%q]6K^^SF;ѐ &=)7N{ӠGMRԨNWVհg-@NwEkgu3k='6yf71>4r@ d Ā?oҗvԧN18nl=2߹-х~tpAu@MȻ}s|ӯ~NH:_-B Nt]+AE^'7&8Mo@Ͻw/}u~Nsi#\=F~ oAaTUȁuϣ: ݧw?uÀ4SOu7;8hgg ؀8|HF|w|ۖ|2|A͇s1FP*`t*PFg}4}l/p/%@$}0'0i(@p*</ ~nO0oD@M??n7i5{G~Hna6>ІnH0vk&`yܗvnP'S 4|vxj8psYpȧ| h" 8W*03.HF0vG,yW0PX (6~.pi/`'}.90 6 7=SoUXƅ]B斆70ؘڈ:P60ok'~f{eݨnlm'F-GnuL 2'sx  릁m ev`4 gz邥}xt7~' yU6p6>BChwt/Opn+PNM>M?0pn8=PؕM0(:pfV7{aQنXinGzz~n0"+PhYؐ6IoPn陠iInyng 9sf-aA3'Χmّ!IyW2p/@)U`(/'n3/#UqVEpFN@[IO`ƅX؅\HXnר`ٞ禖fn9ifxFkmHk(8M 86I;t-x9"'ʀ)+zn3zn3Zv1*0f+Z`9dgyvي.pəq9Ѓ8cHv*fkOnkJn)T #{<@戓G~&؍wǖڅ@kډEP.涇I憣fyt# *%:)ڢH&*h&馣v> gBfE F%Im؂:4U ؄-y<٥ 6 '-/iIP oZ.W? vkhnxW14>nn~}Ɵ*@Ч ۰։3@<4.xG`&v16+9z8,107.y(ʢ6J8;kY2jW+*Z<; nm gz`?Fjꬍf-hwv@hvyY09iȜ.p }-P'nvyjUt*@g=+&enK{K~;nrۻ?@[n4%&QQk:۵:ʵ\[n>;BKg6HۀJk勾&䫾'M;66]՛2 ۿc[m A!{<@K;P9%з$ʄZkC<7_%1>9 +o8LօK??pbynk+U`{駻@ܻ\z;p;40nz0:˳Uk׫v̽GK˴뾄ܾ, ȀK ;,; te mƪm9fm z`\\Zp|ٷ}ͩ$RVnU>i.Ün]AiI +KŻVP~W{n '8c<*NN&uZnu8p{= ^ ䷗>@`R RR>咽ZwJnHX =i-~-뭎Y=<EK`ljLPgڡ *Mۀ>o ncZ/!wa&vAzS8P붞=/`\PPy\ U)uYnHC3\ &0h.Ž붰{INQJЩklϭgڡ_qorpiPA&}wx[z˷ID==Mn?$m&6c~ЌN\PІ@ T~in>Pn7JQ0.j35ѽޛq?Z(. O 1uݶ\z}>~i0&s_Q`iqnoin< ,g1z|_{ o|bʖXMx6a"Yro_pAn]N tyg_Ltw>:W W}OwρLrOs1A@ DPB >0ED!aD=~RH]hr*\LSL5KęSƚ=gNxre >ET͝w>jG1:UV]CLقeˠ*VmݾWn[uśW^}X`… FXbƍ?Ys-_Vfge-;gҥI/թj;s0fZ fm7l޽}SeYwFι4蠢[im uwG{pBW_rΥߏZ;xlm6$O N53hd;@  {BH>O슽E:z?O:/7~d@ 42A$˱ d2%<2&2I&E/|HEjL3=/GͤoK/I4ݤN? s } @ Pt.ϤR1WbѸJ7(֬MQ}Q9!HU#=RFD%Y-I[g TBT$Sa,256YbTF%.ZiZkW2W`5C\[Փo-WϭUCv%mU/4{p)Y&`F8afh8b'b/8c-_CI8LCa=ٞVhe_ngf]fwVaM6|I7hNQ9afi}~jRJ%ZjicL{V&+nAA{;ѴUhA ZH,NB]2|NhvݢUhHJ2{9io}whǽr7\';ܥ2i:[aE7|^(gsRX}s6czM>&ߥR@y/)-A Duc t6T}) JD8B~$WES$&H 6#%;j(Ded"n~)aW /&-ؠ8j&)Dq,cO xQ% o@z.Fc1/dcrA0Q$c )H864"7IpD# INgs`@/asbT``AɀN4bÐ%Ƅ2LN" T*PwE(C{0)G8Pn ~Ѽ6H;#.ƁN鵠 \`-dF aŴˬ+T$,|(npx] ?$Xnd 9R]ңf(H-iRԒdt>IPh7iNuӝN3APTը2OӠ@8UjR ԟ2W}jUZԣ]jSաYWeAVZ9AXt&!O`Ag > p# <$7vղB5+Y WvH V!PPc v90@D}<@E'H\r!a׸Enr˞XJQݖqrJ ԻnM_ȬOŐ|3 808̘00>,!VY/T)0)oVѰ j>sH@h `0A&@']0PREg0V߄G:Qz+S0H~g`  `(0]]Sssb`{9FLKS-2I:f0&f-pL/j& 37>o[ j:|{^~3pLY / fP<%Ryp82uk#:וȥ x_k^tYef"JNܾ{TÁ l6vR*@~ceɄZZ,*t`9)*ؠ>?,a?Gdf%2`X=CT6N2l-F)w$Hnn#:/w6@`;S-:trD}hx ì~)W%=99FX ]mGeKQ$M` Ec~Mt`ү5U)e7F `=Ճ5&uM$Q8ua]C޼^f.5VE-_2Nb.dW-\+fUf:Y._/fG0>5TPN3N^POY`-T &m]dH?O#=[ZO.UBփawDdndDoId PL&\">Y#&b]F%hvv>MFGYeevp͹݈M@]4N擮dc@ ?ݓd6?ovs~qsG^V& mxyzg!>6~_8-ѬI[^]֥h|H$Fk]1xM>i3` &?HNi?k?F&lvF~iI&iqlʦllhmv(VM wpm^&ǎ~Nm&mm&6FVf>&mǖצ{Gjk苔HTkdiFUd뽖fkcȫo3Cp;w.*&{p `픰&X p&(q7qHqwq AT; Gn>˲l|ok~o&^?xi4f\AFIc ~[5Sy>s6Ga=Ǻ)r ﷼sQZqG?GkІFcGbGFDprtwFijp 2g37uQsp89Gsy^A^>~PnZZBag.(cOv>PXa7g798j+h6.l(n690wsko8u6(0nlwy>uv.w~oq'wxRwSeYJ`_`[\]GW#j׌ay.P%0va((gvi#8(v'yoxwynly7ẍ́GxDNwPXGxNNsj^?r_ {hiyj{|qzd:zaxPnatLhy! Gr3Т5,K3ch *WlV7p/n8rã.[ݹUO1~8WY 7@'_2bHa{NyA/pNfx *ؑi Kk>F[PbQmU%7"X(b)bW'6tuIx{vyg i iTאp|6_~٠5Uh59QYj%KpaN(!-E(J$Nm m M]$qJ|Nw&tZh,:ZJ:餑،2҅xvu6$  A0ƪoI$;Eeu U] ;,ŘtEIQ0;%AL\\a\` gzxzFSjF쮻rWE)Y/gi^jsEaATXDĭw8) Q%|2H)EFIlD̮&29'7Q6L.<BmMDwgYۖg515U[}5Yg 5Ni5a=6e}6as}ӹ]اquّi `_3bsL8;Fr#?2K2˅|HKE;;8r~wn&^r.߾zm4V0ρDH>, o?( #팣; ad0 a|Si@UH#l A$p[Ȃ%ҐKkL9%k@s8"#~ HDD$cZxD%2qtE|O{f<#n|#؂"бvc yҳ'>vN ы&Ё0ecFR IZ)щv&%lHVdls>YLp-Zqh}"R 9ll"9UhRCdUik٦ ԥ&UdʾΉ2uӝ<Տj@ *ֱ&)zʇBt3}+\9%%(!YlX]*iI(Wz>l>9 ƔCL;+$ a ԩmLKZQ"J R~VN*qB 0 -hjwX[o;ϧƖmd=.rcV(1 5%*jRq*B0ŚsQL7񮆦|Bӟl)5 `PMH,ZDK"Ҿ;(\u)Fp趝V1Zq3䒸%ZփD}q\ >.N 9m(/k^Ƅ(c^a(8O۔. \^ 8M;mO/[0X,ՉH8#m-&хyebHӻ. ebi`&=!`EI馈2mehV5[@)l6KDmv9 < 6.+vzB zs}rNI'䚈T=qEڱ} Wumdm0*[^Q/kmԚMA.g?>s;!;E  =׳=s,"[/e r.3cr1SoSD$tҨQXkzZU QuvrjwXcwHcr81\ \]R6N/OEO#7b3^"4 Ie;%-gvSR Y0vhi5h㨾q(k7ktlslWgD"lw6hx@\Mr\$w^dvxA_l|xy"_]#+*iv,A'-D5jXzM(V7sj|6}ˢQȧVCvj9$DFd[['nE ;\qrHOtNrLFPQz/:xCuuCGh6 DcIt*ky9fꑪ9IN޸55T8:\iMq}@ّW&)HO/E!c+x#6{'yH6u°9RyvǹrZ^Td10+;4#Ny{ K27f)|sp@1H#>Fا~'9:z(jЖ@ߥzbgy]Ѓ;uLJtKnknG$/Q24sp&3:l98䡜${<{ W[SFF[iQۨ(p󡁻OͯzA̺ÌMWt-<$cp%K|a;5c[VW%A}qQ< 9w:_Fr`&&Z`7EqPM(!uc`x i8?D:'cGus}c׻N|@i;k?>[b_e#Ó~{1Pط77 }~?{;@8`CM8 p"DA~Y&MD0ʒ]0 ͙8fO?:hQF$U)Sl1#H)f kW_;lYfyC{{oVZ]wջo_PaÇ'.d9DGN˗/GƌfϟAѡ?PukjBDn-~ᢟ޾mbąO,>>8ÂӍQ#G|LȮP"(."LBu/pBZ%UQmlUj _M=7jLVeZ6~*J&B'o'dSrM&9&R7^")%b }~W x`) NB )1 PM J 颋¸+H4R .0"TOfi 'u]wncfm=ZWϊ_a 6g3"dZdZi5,zn}/rܶAfvsB;\ /| ) O\p)hƥ\):[K, 6 c#X1OE LfU}ʢ"/rsLum އL.5ël-zZX!hԺk"[+j?֪A'zjn5n;z[Ǹ9r4@n1 'd0.duLWTXюT+f0h. ^i) nw-Q d {M$G%8W޶o?vՏe_ 'bmL2E9q!h9tAdV4-(d'G٧^&E N$PO!%AC֤򒁼PS27+a<ғ FD2^M/LaSV6T̔/MiN=Y&ЂH5{ė 6 $!FyFNĈ zf si9J uhCІꮡ EBtDp J3IdR+uif'֋AGt tNOTD} NR*5JEuS6 RTSn]WV5P%TYCrk [p[ۺup: ԁq'Xd+cZPD] {&H2_BT6dt uF-;Q.T -i%ڋ QA 斷un:H bԦ8-N]N4Vow;R-nf45yZzb Jι_o+d_/h_ o%aۨX/61\PHV=>XSd\-M0M"$htTxťJ(жv;\Wz׸&@a0$2@VN'SN֭ x ,S]x"^ef Xf5j~/|CB1umr_x 7A~ u:`L mNCD1ͨmBcnf3I|Zխ 'ڒҔtKXɳV>.qZv'₎pif ɷo}wHO!&7tNBnN6`(Ns"BgDMHR2NѮV;03,[=[f-HUzH#ag{kȾٜݔQ=k|_2A+A7~JuЂ+; 0}o8=o&)epxAxO+!&ެp'oeK3~=yKgՙ2\WMsFzDz-t;djmF]¿ˤ2cZ>Wcf 6؂d|7OO}OO/s5G7$Kz<.y-# pHEX~/&bvYj+c89ϫD@ <o -6 ^`hrHr`&`ЌhoP 0 0c,kRxY 0 /0pr(o x୲`i` O 0 `q o 5&   FCP ' lPG|^`  aX ̌ 0GG琞0ʭv  g `1 $h g `-pq <5@ ĠQ fQ L̨"Z Qkd U%ʋ ي-/1#g@ J o @q` Rr,q%1%&0&Y&U2'_rH&%'o Ѐ2)r&, Qi 2+r+++r+3!²,,2-ϒ$!+KA-Of..`.` /3/50%c40o#=r `o1$ $qv%2ar2q5x5'1s31'g3RF2L '$~D2*E* Rr8+Dz839+#A+2-Os:D--- -Q0s0#3 $O2%q4Y%Kqpr2ר'34 HSO'SSVSЀ(bH kKcAE7*frAb9-4,B5tCѲGy4q,Hu>tt  b@@C9tTpTNuV\f3jyyW1c]4 3^!>%~-B=ާsk;52ߥADA#v>CDLjp&gwA"}A"|>~艾>~陾靾39c5oG, Ȣ/>7vmmxd@C"\dvAйĞ˾Ϟqy!~ ^B, A2AH$2!%-m7w=}x],G_Kvu;W0;gySO2v MBr%:6sg@mIfMpQŊFa2ʕ,[| 3̙4k̄3Ν<{ 4СD=K#9} qQZ^|.N?P7ά:P:uլ qaKk.Mb6М{qzsEI( ^x?r_reND9ocmqƉȇp._Y9?8Q`%Mh#M`H`` H>aNHa^ana FxOTQu"$8^lQC /hFchVX1[s37=X?Z^A;Vtuee^~ fbj]VH]oq^\Gg-Fsw7Ǚҝ%7rm *Ǚyk飑B4wsPE\ב~+׏ 묶ުk ,X"$X"xϋfuUH 2Y$^^c>֔ATC:ְc o;ZDjlD[G]wmu2s|A“rsrmq]!s,\q!]ʑjk, tKtFJ,ŎhK>J{A58[5^3䪳ˮKwvMo rfqN0Ç'~|ċ~x Z\_7.prL ϰsBFIN{W({m F:h6Y\[sCXŲ?PCKeqw/}6#~71 ģ?j yě.?2Yp6+ՙD~Z9@n?`-mX+ኪ/o:!wU|DH:̍t8 $p %7)qL4~A|#$6\qaY 85D!R#lbYc87n@t@1t]5Ǟ !H܍0&l#ލX9 .IoTCnl'? J{M)X9uNâ '8j!D+m>NQe+sɸZ~l~LdяYG^GfH2ljsP" NC.,9IMrEQ$&3INr'%g6DS7\GpqjPY>uB? hB/>3@QQz|&IEǟ)0UH9Θtvsr0!tWQqs0J'h{msZ-{Pf4UW0u\|2WWnv}M] e }XF~g~w|w^x `x R @i 0w y怋Ȁ8WHd#F,%F"6'Tk!d/&P&sur''1P ')͑lquz')y{Q)r'[euu&}'f6f`b Ǹ1\` bȅep q`^n+z r~rq tv~x R X(zNgb9D/AoY9oԊRElC't s0rp2ØS7:nɸm]_g3Ԩ 3go獷%vfvi(! ~xvŽ g!8_ ǀp @^Hg (rII +#‰$=Eq)'>QD931Xs ÒQp1) *1`-6n n[7f6Cw i1pu2P.9&@+qLJ'p !° hpbR ^p] chp i p9rrYGh4;{ٗIi7I>Y*P˜y8Q'TZ@#t96&ϧjŚ"&)v *0z/- )ʢ!wB_ rT!D*rr!qEiAk9jTyb+k>eRJIiVEՑ7FuZhUjZY7%Vʨꨏ *zj+)0*:)%-[My8cyexzzl*IU&$E?'L鑎xQY*KJKTaL\ MPʭZ!1Kt0Ǫ:Ői:&#OON I0N@Px*F{;tɁP}PլTح@3aך&!+#;&>):ک):qJ֮$zH?g/t:ZA{K|r۪J֊;Y;`R;a²3[JT97sֳ w[B;TYs0Kk 1M(EU+% n&1@##q   ! +2X\{ƙ y;3gR ʶ,prkti:7xeB~ku[Y Z5*Zf/A,* Je⹘[;0۵k ©*+0:CbYۻkko+U͋|;ckG#l&Uz$Akxᾡ{ X#e.^[(Z|+?\:(?rN<8lPi>0|c!3avĽ)>؁:lҥ)1"W߭bvr-.5ߘݒWn3sFױEm^Qġ 3lDqEuþ(*α؄n?X< /4< ->` 쮮j@ka ]H@ ;*QP̝݅+} 44)@1I%O(*,.0YLY3OhUɭMQ &Lb/eN+؊}WYDN&rO7~v>_l~?}+t/Ko)P_7i dY/ [N2^Eޣ/n?.Nt߰_ʲ(Vi {@Ɖ_ޑ&4!A)V`C UxE5:Ǐ/4$D+pE 1e\ѢE)`Ābϟ)R%ZtB EeԩQN>=RYp1A ŊB!Z֬ 6[`P"CqZ]y{_.dȰ9,fGNȑ#/,Ǖ5cY͡+;h>0ؓcLp&K`oubܲ!Nw0 D5oDӧRN*kw*ڭޝ(Ժ}Hvn j޾Y)a ; 1[,( 4E̳Ns04!rK. KLES3&!PM*4m S5Xa4AE?AS @[4 UtQFuQH#4"&"2UTK;Rq<[b\*U.kּら% .5$ %".P"8BcP2de+JCSj[l6;;6!(#W?+(4*:pÜ6GAKR'MNr@ j:R= BHrTfW(`T [#A/w(2,A Z@.j0IU,N4V,.[Zl LܢW0t9@@n #BP>H^$ZQarȝH0hHR~T_h |=R VI]c``ST*s N8MxBM[ KW> ?LWm7r*d!AwU 6qwoYw{,[^*?u ^X5"~lt9,PY0DXŤ{da<P;< (J⃡6"-Rd2tk1-7& Exq>O >0Ceʁ b9&x:e2qw+WœoYZ%W.;A 0+K`f\A ?d 8 YtVh;kѝPt4gAY8"e;[6s4=l}{x5 Ico>A%|萛T>%s p]VX {WxwϪk&,79/Jfߘ5ϒ a )DR#lD+~H.z!fciG?u;x6m#=Śpk]PE9a C`'0PR\%er._թWr]@)]+w}v잳sgD-cwV[&0A : $@0cT -FpCr<7`={ *w,~w vC+4'эt%j  mKnr[l96Jr>a=@V\R,[ϡK}EwK,F?YfgVXOwn֝W#Ӹ 7DxS$P)p_(;8C 㿴? s(>nk>!ʀ'Ȁ 7>+:9 :S0H` 7?|T<d09D@;=̿ZóJK"ҼM@+1#ӋDA{{ij *a{ D9t p(0 ¡#".ajB$|©+Bd,k7x1|3č,85|7?A|4;T+T@ D9@Bd8mĬ+S#1ӓD(|4S[$JT4ALDN\9Pz&P Ec LHAH \"B3n&pbs*,bcLF̋eTghC2F;# XoC<:CtJ?|GsvC< D$J=a$D KYDbHz E"!'r T􁦀 Z4[D\<:"'@'pIAaLľ̾IIg*d y CF#2@Ʒ;08D>C >CK?<:MƤ4Ǵĩk$8LX,a"I[,s:(HI?̪]ϠLlRωP.  ʰ[ jDPT7RMPBٳAPu57m59EH̜K!1Dȁ*TBA??mQTQRh+'T8OBh4Mi,E-Bt 04m(5CP2N[]+H;-ܭ"Sѽ&Vѝݡ+:dTT)T*++%RMPFEWQ qǘNCvmWw}MIWxaű=mi e /f/VVs%*V]X7؉o]PYU峈=ӎMӈUWU9A|?@e v௅E. F7ѹ`@۞c`Ta_[[:T;Mߊ_ydAZ 5v`-DVUZFWcHFO5d]aQvRnS~X=deFe)of8dFe@&eb$7dgenf&cfoVdp=ngu^q;]6gC穅eE]}'gJdV{}Z}dgۜZքB TnhBvgtYn,Vv,^nFNBƑgΥ`i\YiNY&yL&^mVeR]M(;iev~hp3g~&ɳ_fVef?m&m&lj|Vm٘elʾifDZ\ukS?uTwm0'o1EFoiI8h2E"t&vv)绸`TvkV/tW?'sum7&!Bw8?H{_{_{1z7{(z/?{'?'qz1|1{2?p|mx?}O}{Xп|׏}G(S_G}WzG/~|п |_'o|(Gggg/|G~mx "L0ὅŐq(qbB4b̠AC&#ǎ zq?Lr%˖&')s&͚6o̩s'Ϟ>rЋ 7#i?Hz$#)ԪPLj5$>^ viȦŢEuөlr-YfJ4-߯k˖…N0ƪd 1JN|C!z}bABYg(nt6ڶo{wdјsnisy⎏3.5bgN]֮ΨSC,se˘Od}ahCu%AQI1BB1H|SСhtTr9vXfDb}V_ux٘ԉܱ &©uGz%ƞC>i{uyXI'F} B߁$Ffdi^btɽf)I.g4AVhoA8!Qh>U"r;ڈ$uCšX/5W2]:ZxF(dUIzu$JBeXj\[f &g&~[Pؘf֙J=Jz{g[ZԡWxWw颣~TjiXiZe/gjKmo*+K#N˦q|Yفs2ۃ!rUڜ-(nKs=;ӔKXa:z1&0\fgyC)4N)L/[A[*5\ެZʼn+Fmr^n#fܚnvN;`Q k߆g-̳sK@cUeqHb؋"&9nuMV=ҩe=jsjGn4te|6$jGT_vw{=&B8ұ{Wx&|A?h9KgxwMI zYXvO(Jk{]bG!]<"=GjOۃC*Q L9P7V\ti_Pd?P!GnҨOQ*JpB2dUԁ8Pm;E%<@? hHLt7ݫrQL}oڊQ?~UjaJ/U/ZQK/1G*KzUIOٯ2LiER]7>05z\ABW.ե0SNjܝu]1? U-Z@ &["])#.w i^N>[1cDV8jd[RD .Wr"LvْXvT٭0[&[T~~V/MY!oꄓ n2gC b3N7x";~+k`B ^ruQc§߆EZŻ01RI\F1QהrHZ|Rz`ۿPV> d }0-+ɊwJs0@me48;].a2cҡrjd3m˷4<0۞kZȧ9] E7Уٌ"9_55r6z#5U'^fC 1[j)6֦˳f[ܾepez>3Ͱ S٢fΥ gwګv Ek_N-mi =k~H9h y}ɼK<*_9[. 2y`69wn`@ϋnt 8=G ڌDIԥWTףNu`Mίe:.wS_w{07fn#^O<?̈_+o=zz׋=Mn>,97s=<R}܎w}yN/7y]~]>}G=?z̏yD/ɇu=(_?P` z"-WZśA`r^L퟇a`i~TzF`6W9`$E 5  5hC8C ` C?` ;a"a*2a:BaJRaZbaj!  =xC5 !PBa!/ C$a&8!aa?a%b!!"b"*"2b#:#.;@$Rb%Z%$b&rb'R&zb((("b*a*a&+F1-X- .!,F#" 0"c2*22c3:c23J4Rc5*c4Zc6j6B#7z7r#68Jc1+"-b;.;"09"9>v#??? 7@d6A#>!::֢<"XcY%A5>#$,dVU\VaW!G"Z6_"`d`&5&X[b\;ҥ=&^ޡ^W"c& frfgnaNcb3h2ci52.fTT>f.Ff)C(m*A(Cmfn*m.$B", ,A" q& Bs&r" t>gt2f0#ئpBg&m&rB'vguzR{g{:'r2A#0|'~~u'|F'!t(#(Rhf!Nh"hzfgbzj#p自hn(R'ƨ('r"r~6lm&dld"O:%_c21$/b&10$6"'8vj#j0i%㚺dkFc&cv$Z*C&` 2!'xi6181#p)`)?iB(kciFi"c1!'r1C 0*1 C%#pB*2/)1*G6j6dbY.+8Nj>ICfl[B0b0Wj2DB*ri0$*fBj%B0|i%1CB01+0D-*kzc)‚dZ+6]Rfa!J11+1+G+*#/B%삙rB08BBl*"0c$lb)b m$mm&m-*m(*#¥).reŦ !+&C%eȚ+1ЬrB/ C2-*&C0,0=F=n "n*:B6nRnz**%Ŧb--k$hjl%V:B2*j+m#,hZ^f&j]rnJ^-jj:FBv,1Bn0!o&Ю.Ȭ~/`"/o5*lj08.e^oC:/;Z+,D/yneu)Ce"_1dbp#G"$B 2>@ g Q7>\6=0Z00^0+hph!#<<a < <0zAT1"8B" 0[#0Ա0.B1g! bs߱#k c.v*p0Fp1B))2.1ha##t;@Ag|Z"LBA"2 "bqA1O0Sr5/Ts6;6w(2.!//!0/5bdSvq}v6q?tH4i29 !,2+ZA7ׁ"6#c2F. G37"JK#334"+4D3NV0SE3T?l?Z=s4 A*SS#C-;BpD_NbN## l64X7BJ#4![Hqu23U[5VrM4NM;WW=0 5?M!n'p2'Ƨjvh|hn(v~eh6k˶gvgmömsn6oew!5V[uLrXDaE|krAa,t$3J"byw$*`+:w$@w$}ߡqR60ֶir7z);.v7o*$vCs(+2V+d&` g&ko0fOSbpBz8xx+n#s϶p%%egFʸ[cø2= 2;BUWB|1ku&1HDK xK%X#`yKy%A#`yq 19Ks'x{7H9 CACC9OyTw91%a#cjgUqS/5+-ڂ)b%/*)Êr:B.rdrêrS#6/1n l&*2c.,:²[01p/.2V0򗲪)1>­V10KWur&<6[A=u25&YF51[w"715 <9_uTq*juW{=O#傸&#g2:/#ܯ[1!;/3&10b/W+5|{;)0i/hoOc}2g0J'c3}$}n.CLk#H}$|fp%{AtOw:VBuG 1 Bt6q#D rA_~}m$2"+2|8|/70>:BC:+T} k{xJ>D$ x`'hDT)aD'::ɑL9v(cLe VJ+Yt9fL3iִygN;yhLCBqcȏCXIi_Jj Yc¢vuTiGd5']4WVd[: )nVcʞE&Lڨmf%#N8أ`u v,+仹*Z ë qNKm& &DG?h 8j(S9)"ֲN)%,2<M,ꨄ+ 3>m2ͱ)qdʑÎ̄@A7!3^6RC> -CɎJ8 Hi PG1#8q-L3hSG 326%5bK蠼YCfNK )ق\4pev(dO9M*L5 5b*ԑ_|X7t!tБd~UGa5MO1 WN /kSXZ:ŕLOłl_48UnYi|huVZӣږ\]K%s k9Rdw*E wђ ;l欚LAla/{U%%uRarE2@@a'tVN;bF#~1,u83úк2xIMSCWS1o i9t !AD0űD0DlZƣs•Q$GY-#uxC.~^!&u>4A"q H\a;'By#Pͱ{! ;eFB1D"wB4J pLHFI1*XM晴=~6QFPJ%4A4)Ur LHD"p[a @D-oDDDK$򗼴/̎4s /LaBS՜4MfZ+&8Mo#<6INvt8u3H=O{$&?Mrg"BK$ MhC^J]&C#Fh.)БrT 'h^HW7eNQ 2AdדIi#8RT6D%DQU|kQTJCfS\j!=uha% }9$(IgQ "v=brx`HsU`)VX>5T3UR5VeǚVP8 Yq*UiA#I-&!dU" BIJPald ɮizvnk,k @N0umuKIX_%;$,Y:-nsQV3[5 S# !xS{w^ zע^eծ-|(_hra`b׊nr9\3x ޝ oZ5{amELijovV\yi0~IΥl!&DZ͝r )L uH|^up$<+1AFe0?[bBkoYx3}ld&FpC|ȸoM\oP04t!)X{'@QSDo{B8 \^xӛJ,8s+€("%ˀ>e  ,؃Brb#>hY1{{>[pfHJ_:A(^Y?ݷMR҈'`HCưaѻ 6}So纳ǹY {wgۊ7bᗎk xK W]%9_x&=m^ϫdg{Ͻw>~L K|A|ЍOp3j b0*ZPRa00P J0a p¡ j 7* +JXj\ dq.%$Pxm:, BmLn2D^0:% ALЯ PŶSba%z1' &1o+:hQF-U1̯!$cˆzA mt˚ */%Z 1(>̚:Q1HqO [oL @pa!Cl-1L%qOOҌm ?Qr"uҿqR'?2808#ALq$rvюAn&i*Q9 !O(."Q0 'u./)1)108RNt+K&ǒjR/R!2.S.,/k(r-х8!5M$XӷR 8*# -$s+ϒ1M1!,C+A%-:SyDS2R2t5zh8ΧhdS 06-7ok1| 8#,7--s4!UM(]3Ĥ+Hb;ӧlOV3zA`sR${ךS$SnR D?ْ?-Aq?! W4@ ;7,6 f_a5B)t A!1*3HCuCsC5NHDS8qSF9?(o:_/55tRV3{M#HM=ws@T>HT(sR@i4*Z(_TLYQ@FQ";dMW3#W"rxoNIC==&>MPCP1P+RmRQQ13/RL53MlG$ NQO_GOYOmb4V>KKK TRUWLRTXW5$􅶳#T3 !0 1Z'OuJCh[ݍ)Е,P 6~ ! ) M" Wp p P3ds.aP 5fЖ*Vdpb/c-vcI aveEcy [ SJM*j/TʚD Vj6~lJawls*m[`lV58s]?"W\u4F֎4]qTTUIUuծZvDM{@oowR]w/\ !_qqII$7D),W*sXSs3x5s?7tF]tC]M-Gf*@YaR+WqDhxtxV5w9]\sAp ")ű;<#HsxbWgf7P{Vww0wpxwtGVyy~~HAIvv׍Հ]| f;aHarkx7XU4y;x7yՇ3_oQ5$NIK iU"[ ]QE7$XqsRߊ89x:xFSQׅY'"5lw5kّ#*ْ+y@7y+76Y>7y@2W_c9kSI9kNs@y{wYQYoyhs9y ( ٜٝA/@WٓWY$ڡ'ٗ%_ٚڗ/:9ڛ퀛W?9]KZKٚ?_? jř9y1yYߙ9)ڜ zڑzڞY٪_ZMYSqcZGzy :AZy]:5ynZu @`~Y ,9K ҹ9W[ Y, 6A[/G{ZϚ;ڒZ-Z3zۘ 隔;; iQ ` Ԁ9; x  ڞ * b;g (\ڷٸ;{TZۘۥ;纪ջŻ[az[e;'ܮ5 `{{۳ /9ܒ­.-'ܝ#e)S^K~99;0- B@p{-~꡾鱾ɜ j>uS}+q)YU~e*q|ڡٙ^ڗ?إ=e=m|}1ѧ }}|Ӊҽ9{ɟ E셀7y;[/[{ya-%\ù<y#?yCi{uYo9ؙ?]ҩ1?_;_5۽E99 B-c g<*\ȰÇ %|B= @a)f&)jjNjotbU)무bO]&TcA;Vޢn_> &k`/);]c.` +䒫M +HX)}Y& lL$ 7p&D, O,q?WbgRI%s,{1(s1)O1ɑq42QE=ȝ_6Ϭtʣ;'M;E}{PۮtWg g7'|, '8/wo|߁/.*_1.gLs滗,usN,oz|,6~L_;amz3'-z`ê1Q,{׺G89|(LTLX8End컘Z?0~|*dI;=̝D0 *T 3NL[#! 21)c< nyx;#q Lb NHok`2(,Nъâ#Eϋ#X3z,FLZ}r$ J1z$G qdElWy:Cs7FND$I=upo&WPT2n* ئ7K`B"#KH"% b'"8O$t=$T?*}JE@ЇRtg"QvhD1τLDPJ҆NLHLFT 3)LeϖVc 5hjQ]%R%ٙu^4cASzaeWV~akF)NavkX$ؓM=)={z68g7Xr67IYYs` M Gpy.вR#8Zub(JAV/w̩=4&3L-sī$4GFm򲑭to;% [9~Ύ|e0O+KyfDhYUܲfJ<$ya2ȭ_)̝VՅ1Q;n]ݥ/zY amno[%~/KƳJp`ܾov pM@wdc_Η]debwxrŜEP$0\'t1*b{#F @6t)rK W(38hE6eQ%gCWZѤ>探Ofe1,v^ jf[׺%gU唑?;ŠNB4: n gʞ嵁2jo{gn'=qw4;brYp]1gMfY1qw]ֺ3]h?(;WG٭4ɊmOn .n/8CMuS}704=}5)^`3GesgdQ8ǎNj/\#9K.lOO󮢼޴z/ohq[;u|d]##zԍlwȞ^tm/<.='n5ٷr0ߺߍAa i^GkVW3sLF۽xno#XkݓÞyٚ{4e6@ lP@C !@ᚤRL]ː~2?tV?\>eȿωN{grxxx uvWy[sy-|yi iO`}icP0!~◂EA~g~';s&-C{&{C{Cc7Xg1x43| Eȗ|bkG|#X# *,3<`z7c텃;ws{i~DqxMZw궄u*'kͳgvv4G8W8aXMYXTXjXsP'UPXPL%X%QSTRL KK(FTh،&Q,R,FlcU!Ex(H!UtU=(3Xx-ESPn1S3OZ@yVhV]WfWpxxWW2PA吆X}10xZH۷vcXJz!tj=xj8ib:y?;d<=4 c<;)SD&wK3NHLrNWk&Dad1+Y0Y^0WeHwp9wq9t luyt?ŀdZ#)ƕLd80O0S}x0g)j~l=~xٙSÙv;ׄWY˷s3}&}1i`0/sq$JyE|<~v0_)sTsyay8oci7_4H=c@2QJ7NكCLdLd&Ӝ 5i]Ify  Z  ? 3 PN ; 1@ @ &po o *D` p!ğ+F^\k vy203P C å92ɀ c D:q w d s*no Y ɐGSVy dǤhj Q:zS|v $C L#}J;pj2ڦo 1rZzzy:`s[Ĩ*L #e1fP7שXz 7છT# Ӫ!lç Gt @ ʧ1ZoI3jj>Fƀ jlnprmBP1?^ASmH24KpdՆ} m1s ^%Jy= bs % 6ti܉>1^~{^}.1^㐾zَؕ<ٛ现]Ol鋮ꬾ>^`ޤyd^f$dʼn5hWL oP _~ֱNTQxH\NUMI.$.\ߩކ->1.NUܘCѽ1^oR0^K\sQPL8xvE*4CG>!x*1V`0VyM1 ڛ ӡ 4 :ɰIM:P>2VnA"1o?N_=1x_PR=1/خlރ=nî;i>s G_fM ^P_  L  ǰ _k cc.߉/w= apm藾)>g > /_OoS)]p\1A@ DPB >QX-^Ę"/d<~H$=$o!-<%F"ar|R`1 ZŊc*6ұcu+]lC92U6AEB '54=m+DMڤ^>2" FڶoέK㓼C `Ɋ=|/^>ҵ 4lڇEGVu䳫[ 2xmaQ4(ablY]ʼnխ_Ǟ]{7vL[8r*GfT)L(c,O%}$F kVN]/_2J+2.)&B +qB @ ?1DG$Q AWdE'<_Fa qwLO k9Ú{;#-.I%dɁ|>Ǿӟ}+秿~?`Git_W&(Jh2@*"tD,<p !C &Da ?FT d* 2>! # 79Ia%+Ljp9 OB0=N`B E&0qN ,HcH 搌JH'nqh5HP#U,L H?QIc Gh/T+*a0$pB%O4&$4!-gyZr̥/ChHF"<&0Y&YX>1>|!2aЁfA$M Dg: }-:f GQJ2ccBSq B$NI=w]LHgzꨁ4KTz8,*"KQq&N 8 )R^J/5!z'PE#=Amfh"NR,թq1X5)KgV 6}q F dOq嬖~(F*πOQsI])PE+QiUժB __0b=S!XG܃1ؑWBj&@U<թMU 'ZDu"Uiշ7[`ΰ$-eڨ>o+H^u%^'6N7QlVE'`ӂ& '%o0䲄+J.d]X2"ZImbd{ؖjj"VUp7\I9`:.Y%f\غjnKI [dQ(J\b^$+bF1nƫ$(1cqu$<\Ğ%SSl1`X$fo.Z M`4CUpz: k\upƻ4=qJBIdDu-4c]4V=xZjȆ-q5K"Nq BYʅ"N{BVĘLञ͡ m+K hgf3I^sJҚVi-"ѧWQF{tuWZюR?Ӎ2/l 7 |_XP~ʬ?+H.Ίx4vb|aG-.`fw|;I@|н=w>ӊj3b)6v9Ɛ9G7+I.oH h&` ]P-0:vB 4腖y2qol\jǤڅ3ā 3qT\ {E&GM$y}z E`HlE7/ygN&2alQ|Y0yHe<­GVѺ;7=Nji٨/A?a^Y];3qZ=Ɔ0'= WKrm{{?zJ0~B=yᑎH#9|JkdGGzu HJ*jH|T$JUZH?ljG\~&m0FxobFX"ehmԦ]YjgdZZ [šE&g^$"jIS'-(A TsQSTDTHUTCF-ԽDTHьJӒTuCS3SH 63U@mPSaV՚ 9<UCһcvMOxmHW_3Ͷe%fmMSPNjSH@UֿӲHXINN،W¼Wi|E֒UבM\2KE lLVPÄֈXWe'X5Y#N5ubOFIY%֗]٫'KOLYWYh(8kE35ҏxX>%ڎTږaZXclc[BHqaתQԙKmp}MiU[^ mSڹ[3-CKmڿ +]Q[m],[]UAh;^l%-]6m=޶Uu^]EYL^ܭ6E߭\ `[s[T=RU[Űfl JIjL&H^H "4~Ft`x,``]%ߎLȏ$T`IH|y,aLJD~W%6IbJal&ΥI-R!#jʍ\`mꦔ'*'+ <'0 TŬ]5Q^ߪ4VXNX5`Z^u=c]wE_i(^dZt@%׫:(c2RŹQ8׳<99N<- >[H@N$DdUdeFbݰDvM c3e]^1nMds֤^wQIve\2ge0XC8TVeGehԘ_~;ZgncFfOSPMh^]i~ݴ:Ufe@Bn6oYN[rGջeSvg}aG1cNyu2|.ia&_V wxN@d0did6Exch;uhA[X*N`>]HH$S(jRٓ׏v`1Kgv]G{@pf݄Vء.RhN'.H^Hz^8c$fEsjMNHVahiq֞kk< lZ٦ڶ۶m>;փV=Ѓ^.Anƃv=No^nFnn6o.o6<>n>8@ p ޾7q&oٶܦN'//nfp&ooFoo'F$Gnr,r;hrr,n-s%/sp;8=Xs> (ppp p p??77q?Bm;Hq6G~ Etqt _,oth'n6 (P:rtr>n3ȉ!(n2s@Vu+nuWXs2O[un3`5ac 6H>ogn'pQ)iWj! bsHFz< {z|mAǃ' ǃw/{{OxE_x/xxѧqNu07(`.rWn+WnOonho}g~WfG~5Xw^Qn۷'{+0r},}jsx5{x{m>6wW|p'j},_Gxl(1JPPeI5aRQCh1QLj%G2xׁ Xc^saYejt_`hEbt(K&Ȣ/H0 N6h!RE~cn5эWGSVieYډA(k)TTݖnUgP ^xAєGTI2PV*f*ADB ƗNEuYǐ] IwY8Z(_~W'{:,Ϟ7"H x=Hb{Gg^teHunZ$js=*ʣ|A~F.H^jUyUNq$TOlY9ֿ^,H{u㠺BXAszh2ygt.ڛD'EٶXWy*QKmM;47-mQy-[uA>QUVma|lb{eq68݂emV؁i#i]yզn}2б_E2yl RE4T̠h&hA8a)7F +Y$'bG<6Qܣ[Ph=XlsHa=R<&2U̕1>4G@Kf0;%[<!pæ]82&ݘIiZrǙe7 K^.6'=3HS#,2Ýt(􀹔JF'997fagi$jNn̔M97r%rf9|sv1gKKB@"ZНt')P*ԡȈRL8Nu*R~8RS3QJ0^*TU[jYJUbn}+\ WKgj] Z5W]kUW~ug%,Y Ă+b3 fHJlR3 hdZ,hC+ъ,'*qղV8b1XFի-1ta k jO+ܙE=*hgJ}-Xذvf_눶ʵq+bj]uux+>WMU{2Vi9!gxV/M}mlbn+af!WɆFYCɲ%+*eRy2ivٳ_ƪ*2YpnSUf9n+WO7\bU~ C'ȅ6kuҗ{i1A?ת9v>s|i TڻkkϺ07q\fG躽y5k e(.r{VِsVG mk o2p&(#WFP->%0!/#"숑[ S&'ə0'ܩ'QTϩ7wa' (zU~r% DTtK=A?:]Wǩr JQ 0/̓v<sak*F wÃ]1xG>䈎D՞yEd>z?_*]:Nt7B|({%^8' >Z )r[1M}|ܬfl sg3c>ف'?Z5OuR}3;˖~W# ))(`) J-xM_YUWke3@2V hI¹{_- !_)2I !=UW2H3 & z%n ҟ z ry"\A֚FZax!c=WY!EuXRyV >bTV\`!,'n[&a n[",j W!"`:44C#RiEbhM"%!mlib'FO)^""+7*,R_y#9zvּMabkQ0112>2^63rb'J@52^c6֕ U 8#W"YCzc%/Be[=Bb$Z⣵3_R LDAf5~!8VDBb+*R6$OFI!/<#J9z?rL~%q_AR&NNU8%C%Zƥ[b.S~$<ƣ4$IVeR#fcVc3&AHXeM M6ը}]n$\eD +&e[,W]UC2%G^HV%4Ce`FaVnV.X&2f1T2@DfP-@AMLV]&dRv&k~y\~C^)x'y'yBg^i!;nj6Yl%3ئ`R&o"&&VW,OrhAP rs$tteff^_{NOnzuW}Tz~zfPRt5e!Ƨlgk5C44n#~&~`oBW'P(0O(6 d(ơfZgi{ZۈΕ92!VihGF%&e)>'K惙!,KdA U) &i4^]\u\^Q՜ݱ׹ݜ 9*j*]uNIQj:ՕE> Hڭݩꦺ׹Yj**U* e㑈]ڝU%Tkυrjkn;TB+AkRu%겪ʝ(N(DJuihORZnWji#.엶.(mJv~,ȆȎj~0 C'>YicP+)Piyzr:6,ya'TY%tr"-fhNh;4v6>-XIZ'lZ3>nmz-R VY#j&f-mjmʮlU,ꫀ >A΢,VcYbfBf#ZmSþ)g"l7NÆ*\mSu*pC.YU#nZq8./m}VF1 C2H(D.JEV2\2ʖrꮂ%JZ햛.>lzZoU0m(1&o$>oggo=~a)>zFtn/^YoU o^0k/J*0nI"b$R]lvp~"0Ԗ02qS)\1ggb op]p oepIp$ q"moq1%t}qU0. n#0%*03ֱ gteF ryuQv'"";2CrJ$ 1"Yʢ'?p*(d)$?rp,2cq" g3V-;-ci.2071'12c7&)?s4#d^(Ui-s+qBTs3[2Xn::r%%is)t2133($?s4?BvC?A3B7g'8w!Ec#w:2/.;o0[0c#*0'$1'%.s/.44VteW%WߴSY)vBM?WkW5cI)YZ0Z5)tUX5`jbrU[c+ڭ0W=2* UUSVKíbZi6bkklim3)2u%XHcvKOu3{egSVq"Qɱf*uKjui+v]zw~wwowkݥwxv+xwxstw|}79+R)Eڡ+=+q+ H~w݁g^EXrrqggVϛ8¸ϸ+Ӹs{MRwa'H2GbwUx˶7y!:9;eO.nIe9(I;3'J[fflZC//x/SC/CB/U210 V/W&SC/a/sB/Ë9Xe&C2"RK6oooG99g)yj.2T/.ze«0TB0 C|Bj:jcU2C%C0'8U/BB;2C%vB2:/TyQ%òeGIZBvyTG3cc/\:K;õ/0oBǙCW2D;0 :_UBp9OE6'å;G'u_U"է y߻廊ytUGë2|/x5#}-y7ū'DSuk|mk#gB˿dk?#'?l_?[c?cAwwgWsA?@`8 -[ ",aC,[\8oAo[{`YͲE,e۹nFrυ蘻cT9_x9݃16MՆ@0M(x"%* 0(0p6h܎+q PLQYtݒ3l r疠0;(d,/( O;QɼkJ2 )H/Ti,"$ W¢ Ib, )N8p(<(d D"$EZ,CuqФb\!NI)K1,M9ݲ U,'T U4K⊐"$Wa=$ZtD-߄Eцhl.gT榭JK͖0l;ɣo Wq-sƣx\TP\*(֌起p( (} CGv ݒE؇!xbx3.*-A ).x->e)*i0eeI^9YhgNiPg6e,nrz塟FjzjȤ>髽:jyh.g~kh'b@!To g)/hϾ 0?\c=XI7aeٖm}]aqy}2Kc$ u75OVQ ^ Z}@=ұEJG[I%0K=__}&GM?$ lJ+Aҷ/t<0qe/ Z^ůi^"פu} ^"(1^Vնqmmof[6mrvwny;G?:Q7ʾKSs/e}j dF0Xj_v{HTB؎H/xi "[َ` 梸Ҕo} \.l-U|SNǔ촉Iمl#8J<ĨD%~]N\=Oaa#@}cJ7(Lǽn8u<}WjI*2ḑ{ /SǮr^UF7^cRtgMLԧ>vϹ9w}NWfUkz֫z~ __Gtlc|Om(?P\+rIFoN cNi0B LmJ8PZp<0 ^IqOO)MO noz)@ AxI B B 0 p o 0P@K 0PPT+ИZNBk 1 q0q9 x)8a.6 *) Xqj  !  !1qá1y0FPP0|(lr|aS! i"!"R!!")#﹐A:B+*[ r~0rI~Ṅa+ يRk  G+ 1햬n `vn++2,R,A})ynC -anP/8!H~ rfK*:@.00=n-d P2(1l`2g/I2]"*A 6 `! 2pi**s*ua,2*a,.o:@9˒uNf-Am4 , -*Ξ,Ж -Ŝ>Ŷ+L’.tڂ Β@ ~lz,L>"4l@4- >m-,4К` !C,,CD,nCDo A" >K ljS7o**#A9s A!:SMs)MCܔ$ n:sOM/lM#=t@9-Hu-$-<'u\i)>sИ,GuD-NTQ-A?4PAEs>y i=;ǁn]4s_u\ɆqD˕ʋ* ̝]M є@eNtPNNuP#+!ַ}~l}qtx, 9P>rG{+~x ~ +^ޠ;{E>{>#aږM-~}[;=인}۾)7p[]>_ZmZߣ_[hA]3?Q?ۙ>A{5-^{鉩饊nImR_l7Y~ߍ{h??; WP ѳĉ8|H1#Ň5.D"G$>XH)4ٱƕ efg͉z 4"4iR=Q)ҦyZM=9 6رRIt@] Zܹtڽ w޽| 8 >8 x7~*5µ.(yAylRŚ,͐svN+6d RhgUw]k_kNhkX ]R1\r\IB8"ux!J1RyQEV$aGP}EhPI}'"8s2^uҢ]= eRNX|͇EiEH2ф`L:i`kxlu@Ydžduga"PO@"3y1ihXH WO5 EJ9^yuQIkުW^uyٛʼnir [f,ȶt'XyB%@~("U(* B(VY@_L &ǩ#X.b\..5> qĈH *E 2Kѵv8ۙzl kFfsvOiUTaVMy 4)HARՋUĴӛvl \u!py ;f}6L(LvoӥvK,KxDG(}NB#qxށ77ΆA N[g8߈ ᦗNyvz{~c^λKN;y瓏NWN/12}W77Q~ߩͷH_͆@+4< FCx5ix:/ xT1.uCJŵks)؈^um,+-e,? Zl&(+v2AK_6 L^A5p'%\S K)JS,4zq^SNH)k^#*ptt R3P"C?! d#fdAL"S(QMOaWȅŤRbR/+`,xƫi_"􃪼el1;%# ıa&:ɝAhsf78c lI$aFJH7&5ɯNJe[ p3lHB( 0eqDTEDU$~iªR0h`eRFlX l,$doT|M͓cQ$Ai'&–Tq(:ӱU/M[xWcbe%uU\ʔ4X9J=RjL͚NM*k+ZsjAjQɚpRSaT|VǔIU/[ƪr5] Zdz"I[V"+!z5LpDv0N><ĝ|$Xo9(Zũbv4cZ2,ܝvSԜֶ[mD8k~j>3%r"=In)Hk%.?ݍvwh? ü@F$Ng,nqOŕ[JTb7qS yD$|d61#c3ǣ(|)JֲLcS沔n6{fL_;33_9ѐFLLHNtUiCqo3Y6wA-jV é.8n3v1|k:EuMhF6ʼn6s]<8*wxǽrߋ3Cݍ tӸucL|gnZfniS{lr-k<Ŧǻ䐣9.[pss9V7JA/|'|?ҥjK~J1\t[4uaQ^g[o_zR<[ŽqONkNNa~h^s̏43xя<''Yės.}O9[G}{$ũ{{/}u?,kʖh 0sV gGob|g}F}tbǁ M6J!8 VlLAK ,6c2?~C3L9=(8M C Mp7wK؄ws-FcӂL`U(vDžK`sV8>0m_8]b_؂cj8D<1h6X6@89`E8Th=<(yXt؇Ah0炨(hkXP|708o(<0xbVx)Ɗb7t<戂h8 )6#"H 6'azȌ})}G+yluecyr8{vy*g{-qhРwggyx|oЧx>xP}Hpͨшb~c{p y i dqܨed&"WrxqWwv7|X)ng |׏(FtGp ɔѐyV7c`~Bf S [ɕ0 Е]YILqvkbloɎ0Ywe&yFV ChM  iI.fW Y9y~g~$Gh19gIf5g W=b8i (o9yhz1UWW'Y9wyfɒxbmIfٍ鎯h&)F?yh9xƛ曩rIWɘȹQٜS l6Wə(I&idɚۉ{zy*׎*~鞇 릛ƞy|)4٘  }1rb j~ {:klJA my!h#zV bi A*iҨǣ,hiQ_f1 ^rIBʖRzrZ) +zbIo\ ) &dZv[Vi٦7bqZZyiF jj EڤrɠK:W* |'ȥ aڢ*tiiɦʔojwsuuej , ѹu*L֡JjzǥҠwJgÉȭZPhjev*;ʫ[*YcmȪ:s[xq:4 魧JrjLF~p+ʮ" $몆 Rey蚞'Y t Ff8[i:ktG)?یA e*~Fk/ڴx *myzzGh1[&=9֏b#Rhr<4 nˌ(ʀH^9~AS؆9HЈ3qx} ㄞMLH+ilX 8XûxEh[{]8c[˃:Jˉ*{CAJh肧 8X:(˾77p[&Lj;yp jHW}Zwʴ!˷#kwaF%W{[g x2[o yekƹ/ٺIgrt{Qk2L{{} v+4̧T{c,<> {IEֳ7C9c`\mflc. ܼz GK'MѹтqLG-Jҏ Εω)5͓ y8{,iʠ@i=Hf܉wm|ƒ[]zz՗ܱIm֋=mjTMmMqK{Ն iv}ڻ|Ɯ{n0ӑ ʓMh=|ٝKצ]ЩI˭<(]S'،|; FZּ]MOܶÍjm׭ܡׂMrۭ ]-i ' mлہ oMAMMEdwעl}H*& +Mn$j+g6-ӉӠjᨌ r]t]I~m,nݱi1]oY7.9gi}kA]k7,X<9;ث\˼ǛItb8۾0(^`"(&6Kν0(yK+[ Iн ̿'6LNC׋c {޿ nl{8VMʞ] )w@. \ `=،L b^|c. ihh hF+s[v0a-=NhNVܾގ'NOSMmn~oc p b6v8?L>e`F?GKoeY/i\Noc{6>df&/(Q*sMPK+hh]'.boqqb:dI/ _8U陵F [ `>>ȟ]-,xOqzbsS&FeOׄo|F|] e"o֏^_ϴN1_kouNcOJ?(3Ni8ӾtQboE_kgcӏvvZUA- dP`rX!'ec$ 3%Tҙe%U\Y)K1eTi&LfĆdyOA<Q K&}9.[`MZUnWaŎ%[Yin-/d2\ZJIfR_\o߹7ŽCfHPU B9"ſ='Ԝ艐;nhȑ*OrhКo389Me>ֹ۵ѥO^zaŎlrχL9v>j0rSTrI&,-$/{ҢHxhyP Up:0M88!iDJ|O@ڳ-d ӱȆ"=*yц$-rHH ouR3eTTOS2UqdhV\e+H 35j!PV'~Bdfc*Xij:³A t\r.\ iFGj(zĔFѱNHW |/{T_"aW`&t͕| DJyB aHa~m٥`4a"]g]y*uYb]GxiڷiNZj~z 64 R,X%H((9&+(+Jji&[pVϡ o="Vȫw|c/&62)fKJVoJa;v[ܪs:ePw%PwW*_DYX"D~ D^zDuz$z^/O|_~wj_OcHX0w@8}| )( fpwe4 +_~y3! W)%L%az%XJe a!{BKյ.I]Ij8{s`>(yE~'h$ӎA IB/ɰSH]k@TÐ %u,̔JfvySx2Q  g%1.#\0@! b4h0$4$iRv#/?F3D9vIu: `ɠg'-PZLHQ l*WRu Iv /-zQ_v.pXaaL" F3aqԴ'x KHFZdb8Ѿ|ǚ:=0 C"_YL&IO}Rk("R2"NZbYIUVzv(E/V]fPE0ĈS0JRLX^ `t^?HPoJ<=gEy u8FRTyݤ >Ϫ3(Xժ4Z% WN0;Z x:_c1I bISL,P kI]y:ڨ* 6d՛c[m}gL֖om[`֥\l3I#Nd_]G$ظhiH|]%ohg$̐3+a 3U'1úE_VԿC݈߆$N/u-|a *ؤ~, {0,~dwYLwā-΋+*,UeZRsr9- (#u%_ NٜrJٷ>8/X Wf(5uOqЫ9\l>yCހCO.ɢ7j0]q#A^SE/&-H@a{^X{5k_b2zAꄥ;Υ' ݆%x 5Xyеxжť"b Ɇm4cnDl^ \ao,ai`} _K{ODw5 L1g߅wp9"nCwэ^'=>b$!tG]SzխN o\][e;|='t~U\4v+qܕQ_<Ȱ({rvS\tg;nw3 9a𽈝~|Y8<TZx=-~ \L,7<<էx}g>7{1|7>o}o>>?~ _}?o߫O{*+-jk3< Ms+\38\[E^]E`_ Fba,F8I9DXP[DFjcFjFl,Fm`FoZ*|FUTE}QbttG@|W4Cs;t\GxԲ}~lL2TG*{|ǀLtȆ HHeAǾ9ȲsټKA=Bu"DuBTGEFDCTOOU(C%KQMPTPSuUT!STzTXRYVU]M@.z@12V0Bֻ4#%SerS30*V&} X8uVR %r5WEW8V"Vop Whvuze{%| }Qw=x} +XMX]?:X}XXBrXX:XXX؊]V X^]HY]YmYՅjxYYYY٘YYٚYZ-Z5ZMZZmZY[ 4Y5ڻZZZ [[-[-[=[][m۵M[[۳[[[[[wE=Y [M\Z]\}\ɭ\\\u U#7]]-]%7H]]]m7׍]ٝ]ڭ]۽]]]]]]^-^^M^^]^}]k8]^ݱ^^7uݓy_ _M_5_E_}e_ߕ__=^^_`ӝ5!X]'8N!+/ja` *f ]f`v`6] `  `_! .a7(`!a=Э` ^_ b"na!.b$N=!Q_5xF~b5(]/`7/a0b$'ba,bvc26$bNU,~4b/c;v/(c<b&NdF&EndHfGna'H`)Ѕ&?VcCFK`4^N eR&S`!NaeqeхЕCZIfccNf- dQfdv)if(c~/ en a>f0*gqf6yst_`6x.Ԋ hG6ff v1d&(&p/߉苆h荦pdj{f|e~6YhVn銦`h茦`-Ves擾gajvi6j.hfM>ji6]v.~ivjjhȤja鲾i>Vk7޶^knk~jNkjknV~jvUd!pkX]6PiNeulVgNl_~Ve^gV&mUhfdٞm<>l!mMj7eCl+0n06heMc 6nN+^ybNfflanmܶm%>o>$6`|!Xa^cj)oЅᓦom+o>G` :p+o^aVoWvos ?_7bEdq[q'Dq 7c %7".r&'o()q$NrIl'r r/0`02'],/5os7ϊ779¥s;s>ȩMqsqtBuQsRJG?RsVo9uBuY_sZuHu5gt]uv=ua߹bsb?vx5N^6_WvW8Ovi7jwsjvviqQ˳Scˊ*qwew wSwstv_wCz:|Kv@wIxww:'dwwrw~/xOx?x_xo}xyxx|yL_y?yx@ouDvmW?͞v@'7ggo5zsYto_OzUGܴܵG?{x{o'ǝ7|CĿdgοϯЗчvbGՇ?׿uԏhؗ/}ecR[}/uO~~~~~럀~? @VfռoGgz g J 4x0A6(B Z8!F 7^đ3FF]~d$Ȗ4e%N6yJܹ2ϡ'DRNB(u!,":[D  (*+,, 4NQK3&! !!!#23 5 7 8 8 8 8 8 8 8 8!9!:!; ; <!@ !@ !@ !@ !@ !@ !@!"A#&B#(A$*B*-I00P00Q11Q11Q33O66P78S0;Y#=e?k>fDY:Q!5B(19,/0-.-///1001001001111114718=0IW1S]3^h3kv2s~3osG^WhZJqZIqYHqYHqRHiLGbJH^MJbSOhWRmOVrEZw6^|1h0g,j&q8}ERwi^mZmYl\hbvhfsmltvvxyyzzzz{{{~}992211111111)Y4|:95444Ø)~%Q-[Ej]q}߻ġȟǥͳҷR4Tt텼! H@x ǰÇ틂+"DQ,jܨ#G||pȓ)6 &bI0cʜI͛8sɳO 3^hD\ ̸YV!I R F& $U!E TujU`aD+.Q[eZ2aF BT%C  I*$?#KL˘3ʹѢK~7Saוf1aѡ !Q(sk!jW@`s3 سkν{Μ)֢ i ֵ']k6遶]XwK6[K2fbE(Vh!<|%Sp R􉴠/\")FQAPB% b,:x؀iH&QkB%tZ!bHiQ&e}jB%q^j"J?ՙY%B]kgިe-hTtUB䡈&v"*9 "b9-|~:j R[))b~"jqΝA_Wcv_f5ˢk:u첖FPVf (a {SP1+koAKѿl' 7pWlgw ,$l(,0,m8s@#<@mH'L7PG-TWmXgu=o`-d}@eCblp3utmxOv|߀=ށnၫ7у+JvTngwT⢃]dָ29v09EKMCѹȲЯ!,8|O_}N/o >1NnxZ5[c;/ `g X2GWi8&H $XA ^0Rq@j(Q4TL/rq[-6>M8Q `@#LsF4 f0Ԁ368}q5.nH269c! 5ȟ.ܡ YpivģG : P&6BFͅBE@IT, |p&7IMʂME.&ʣb|W9O: ,O:P*3|q cE-!8HHrE4f.+.HjZ3i\\MbHLA)a(D δhUlݪٖAqnkhsˋ!5FVS1e۴2F+^e.7Л8QSk]lu&jߩ͹PeўAIb{zVp HZ l+Pi gѧE-4Jz648,R>E)H\4`xW-0e_(b@Z\:hS*ݧ&ƸmleB r,m"s+a{T6vqUqؿ.:m o k&sɋ42e>o4MR3x 4:ϑE(&.IA*JEmFmP>M}jTfuoN1kʽ&it{ <űAĪ[϶F+kO;_/qnܲ}N={Ts[5H=wxҕVL)0_. oػ qhkH VS8Õ_zϏx=}!i5~ULdwWN}!u>cͤDo('Fv췎?l7ecoNvWc:xΤ g4W yWHPx 0~%W@5R@Tg y h4f@jB P4TJ7 B4{4Y|0 B)|ȂJ p|U0hstnv(lڥSS@TC mlx~_pHgYx~XUymV福(Wy4v[Ȉ h j0 QU  y4y44 8 4Ϡ ROOӑ(5P E9E#A Uj ,F0c45yI4=w4=jB9VF>u ~:s@Ibe4L picu䇖uXnX~EV~UV#N[b t3j@ ِKA0)PU㙍4 749qx-4iЛOc(4ӃJ=O uzE YK s0̰]5 `JSECc~yJIW#e<o&o4I^JfP}W:^J`Q3W#pVe  8V4=T@Dsjd@G iK3 @ r@ /uZM:5;}Ǥ#RF C9z#گUAS{7  ۰c^;. [#5ڱ ";$[&{(*,.02;4[6{.4<۳>@._D[F{H3CL۴NP.KT[V{?;X\۵^3Zb;d[=fl۶16r;[vu t|[5wGz[<+/\Ug3Z8r2\B•":S\(6;eCAA ]8!H]SǏ.K+u~/;r+ukU u!ԺUuՍb.һ{/{+jcl};9&ࡻ;LR+7h7ە]ɅԽ{38u uE|2޷Խ644@B>D^F~HJ.G9n5P^4SkTcZ\^`_/N N`Njlmnrp>vu5mY~^n}b^~dny~v.n>k.嚾4#>꤮~~疞jꬎsT~뛓4}>n9饞.NKS壃B~OS?O^  n挞kN.~tlN^~kR+*H/?gP'!7u "_.eO>j>>5A?>վðELO E5o0괐`Z]B0 `a?ecB C komoi0 &?uTT" h3OBoO__\ZS[ & ZdT ]> p z-~v>pwEu_H7ZY[a)TaE>x.ON^oaTjPh :S2tTBmQ@H=~ R*S9ĀNH<ɒK-YeN5[┩SL6)ҥX:eSbQV@V]~G E ,ȩu[Xd5/ӱ` FXbƍ'{kތ, y@5Fh%˳VmR0kFWy$/nƽȒ&Q)Sxp;7^|c*7nS,2}j-{J5x[w}[qnQϊ~0`„0@>&Ӫ2ZlA(6D6B 0l-rH1>:); [ꖃ.Ffşd;H>JH3/JJ,*f- A/x'x'~ogyui ޺ji1 S(Rm8}t'!,#mn73!πD`@x $8j 8{Gi0{W;U% -l&G¸͆7!$GB ЇC"r<# ǦD[=s"\d(-.%9bٴ$ы (Fq?3~ fcF5ь?(;k#h8QrW8,&6RbW|{5IfLeb&5) .닟bRa8Qz͈JEr%,e9FZڒe,aDveosd2Ȧu̙LX 79MjB&]e6aF324%8q,&W ;e,tH+VӞI'M~:{4g9P=g:ӡ\CD%:QVԢ<vԣiHE:RԤ( HZDi8 е" EP7T.uk^JElA{,jkpq!*MuǨ<)fYDkhxWіִN[T' P]FtŦ3:&46bA~}!!(Z5KB\O򐖻/Xi3Җn3ۮ6j _M l @B EآEҭ"|l1J?0́2X\E^%F1ǫ V#z+!6Ø(P ǎ|Q1\ƞD:ҙD׍bL(_2 (NoN9^&䃩e/'Nqf6v~]y[>VkKE!fjF(AS,9M~e>s1 *⾟"qJA.3@'WIr@/fP>155@T@#O @>͒D@YɜY kyĐ$JԮLQ# B"D$Tga|BBm+BCpBQ-d/|'DT*8|&gz?C98|  d :fi˧<:rsT(+1 D*B,T'*Dˮ{0\")`P@MLF LMT OlB'̹˘:  M RzE ?p00?P;=ӫG۰AE\.^dD Fa9YK tF|F˛EA* _; Gڐr,r9XB(ò03^ 0=10kGbZaޫA4D@>hH7,xID4#8Fs4:#˺JÂWB71Rҡ̺Tw'fDIAi!&T uP3Kq!bSQ@4U,8f68vI-;9T;ý[;#5#4É!E5!6|NSS#:8RH8kA8Ca[Cb͸'G#%Wki%1<2u\ T"sUuev(C>I@?\AOkxAr.R}7W){NZD`]ؼ׆F؁>Z|=Jؽz؎YX%"؄U ٺٰؖY!>MXXZ؜YzٞZ:-EYDZ]1 ڢ٦ ڙ% С c=GVVGwT8GHU۵e\,Qc3w}W(ۻےש-ZpdL*G9II%,[(C[\uۧ M'BU-iO)KXUxS 5P2S4]=Յ#]ŤǍ\ȝ\%WSEf:SYѷeM\E\ѽFFªfÅ ݕD : Q5ڭݵ]}7%FX8@-^΍FkZF=)IU6\t}łM`P<_e_H` vM;_PUQ 6S}1.R}==a.n Sbw<`5D6@Nܼ`gQ]Sn)\+#N/4NPU#W`+Gԕ%\7l#bf^^OD*2˱;#%iGlm=ܣ=[Modp9`:N);!n*=6aĘ)]1dD^md9s^|nI # 2vEطabFtJn.pN.l-eAizey}6浦څ.uhfkAH;kc`[yCe>9Uk,lfnJ&&gp!2b3%&AQnBFNFvi_Ę0G׭G-y[UoN6Q34Mbmeh9Vt7 q@R1Mܠ0_U7q2o4>^e8}^ G$W%g&w'('/q L[#r|]vlnT{$Jq\8(r89:WEO{*PĂxt; 8ɷ83I~msU#t:gh~d$a,/ t0:/]mB䮩uxۉZ(Eʏs(bw[8?Rwzw{Rpr6hq$}hr^xvYhGi76^c6~T7~q#9 ^& 5A 9d XI*dq &!QBH!Bb!S&je5&^vy[^bp(4'<$)U(:(J:)Zz)ji1 *Z**jtPe+Gřkx'\+p9\uzjV|YX$uXEupBa)骻.E;/{//֒/i&RȲkԚ&x}f9-ݚ sLH.1<3͝Q93v p!:E,Yt.t 21F{tæ5Eq9{XL"gry{~V*=dU3u}7}5UVh5Sp чІ#~+eJ#)^/yqY6NZC8.vwl[qט޽\7|:Qc֕@v#E,ŒC݃/}pƧó>٫l~jݎ;59(MxpuB3yAHcw`f-;2KvI kǶ?Ooh!Zk0ʁ0DQ:(XAmNFx- Hԡ~[b6d(_n~E" q@+S`W I,NPc1~*edF/jPJ[#5ܶ<"w%.HyCW@T<LI*"0HDb{aMl̒b&1NpfZZBYZLNxųG1\rMa.*PK풐DPX$Nd2Z<LcF@Rh9fQhR`.})LcRҴ-)N_zSS-D(VJ@8)BNeTAPB^ mh"-j +OLFExB MBB9}LӝʕtiOS]DG~Q"(w繞ئFS*:̊@$VL&ڢY=+W Viq+LE`a?Z+ӻT};me[ / n^ѲW7KaUK. l5i3Q:"%Iֶ_A|W,࠾| =r\??珯 u$i/ɂ,%9}fUX/pSGBEݞh5m<*@+T5ujKi qmv m q9] BյA=Uq"*B94P]TF%R.pE5U#1GMVIxAHQЗW~ ؙ~Z[zFERq=F"*#r%ٝ$#h jgǚ鄹Za6j2?#?jbڠGju!j&ڢZ*zVj i? c=bf(b}R3:YQp*_)i_hjrYNj.۝J*mO$PZ(b(fn+v~jƬ䛹4hjҖYƛinTeF l*,",{c2fMFfbM&Ql5˶˺5,n,k*B,%'l~j쿬, ,Ĭj(,Ҧ,r(^ [l4ڮm6V+[dq*lRmծbhrv2]-=6@ǘ-J,4<B>m aLDݚ+-J-vٻ.r-^%iZ&lp z::ܮV̢EȸB[炮%z߉n~mNFY.4.,n3.ܮ&R<-Xކhmn>F/[f~T^/B3 0pQ4jZDRQ\fY6on.z "‚-)0kiBEgN#q̮%,30pQ0C{Z\qEcA4].@(21Gf0z)]1 Apbq .N6e~0iӮև0 *Mm[4( /k)&*)LaC$&r2-!e"SʲEWf`.nv&1 w 2+2*@^j-Cj>^%%Ce&2 G''G-,-(+Lg*%* l+!˲&"FZr8B1-,LL.4pJHVnGru+{uCrX!+'nF`^[g^s4__0qU'acmL2Jd348[6k:g6gwE?/6iSH/@vl'4MvjVv foowp+7q xzwK-A:6Gt*(rпt_n_ vswwRvqnz;z?7tH7w:{wW7vg6Y[ub_ "8+Wr:x|GrIRY~co[7K6' klj9'/97?y{sCOx޸)kj p Rh8 9,(yQ9׹߹9y9CzÂ3x}W#fj1)שO%^빡zz:˶7s,38F + ;{0D #+k$:ktryy:ws{;:7Oy$+ ;+)6 )>;.B;+@>±E79O{9cL3C30<+H{} =7i腏5_a:+¹ܹ׽yޫ+C|' {+*k E})ч9>/8,DEW}O5>80q3ÑcBzj]tރ͚EaT%qzBua%j+).nkVѤ@ yRk͒yy0y9Me3mAhs0/B]xkjyY Pou:T=;s.xHϘZ:.[M)Y/qfR %qY(.;!ZHRE럡) tE]PՐ!Np/ymv d`0umHw;J&Aw漆-,K@M /½x/e@Ezd'L⨙Blgi#<AŀڤL-RZv fU:* *o"Bτ *TJH_=҅!YCxxI+AslU.-$cbMVPf9.V1n^9+W=_A|3nHHrqW9Ǹ‡&"m-d%Kl5#y7od)h҅M)\*X3r$! ]W$P/ +PJRP&& 19򭊾DM,5Asj4l0O[So /[P q P  G w P Pp p0JB=O p  Q1 #qQ p" aCQY0_apkIc1pCM/=pTc1qG PuQ}p 91q OF1GS_ rq ݱ!1 1"=12ɱ!M#-r%RsP%ŏ37%G#a%Qr'c2S 2$I(&R0 #ouR2P2+r(!2(q, $'/0P-/{. /I#.R0o/0!s11 W0"S03/7/;S/'J*31%>/OS-S,W(CS#412c56730-R0}s 63S78K85r'r8R7s;S;W9R:re6;S  s,F37S%??? @ @4AtA/a@A!4B%A%B-BtC9CB=4DE4C]OFD@OtE1DYE!EatFTFiFFqtGQL4@.>s=HI}==MsHH1tJ,&KtKKK4LtLɴLL4MtMٴMM4NtN㴯*N4OtO O5PuP P UPPuQQ5Q5R%uRR-R)R1uS9PjS5SEuT4TITAUUSMuU]Q[UeP?ScuVq5Po5WyUOwWWWuXqV/XUVuY]YUU5ZOuZuTRZ;u[5UW5\\y5[3\su\5Rٵ]a^U^_]']P5_ u_QU5`^%u` O6a}aEuaN!6bcb,c5c16c7c/Vc%@cGVc!D6OBekddE6eKggwVgmVedeve6isfgiiivjVjivkoj{kkl{djkVe6hvl6llnmlcvo6nvnoek!^,5bmqqrUr)`ݕr#s/Ws7ssrubttu VuVt?tuUvvvuwwwxUxUwcvwyWyyy xxɕzz{U{Uzz|W|u|+| }}}WP yu~yPZ7ZY5< Tw T8 4` O|,xO@go !J X3#! 0~}߽~ ث ^m{vI'^`^y˝Ӽ~ !s~9;|/?yRC+?|Ga_w?q:e^o>>c蟾ԹV苚>~uӛ_З?I=3vbIw_>]q?yy <0… 21ĉZ4`1ƍ7b2ȌG/Č;8Nj'[ /W :ϢK[&m:cuH:6dٴk۸#ꖏx~& s4BVP!]6lBzs^Au ryerׂ \E_}pXڌ4Weyq(vQ\EaW7.!嚐ui$(P‘7X}7f^^R,G8\rq⡶q Yװ2{nBYAӘd%j?\gH\-(Y\ V1[yq[n⋓10c,0&& zLrnVv\+}r*K&̂E-5`TW\ǕR {w9-(Ml0Pۍx՟k|ހU(guoÍ7uS[2k3MU型7za#~W+KIn{Jw4DnE.߾/>z/e|& 4  # \X: T! t AXB `VCP 14eb@?;Н0&:e.˹:c.+p5yiBs:Ϟ&HsGeIV H|)Da qs RG.*+T! 9$vmke3UuAjW`z>t-6+`Jnmn[ &}{® -,) ]sr 7}$qP Ǹ]CAJqogr/x o~C[78'5X+$wZ4md'>ã] @f[a9 ^nݖ^o/n;vלϻ͋w M{c)`>~y5'wɧ=V|N|/K'/HK3KM)W`<$=Iٻ^ (\s_bO~__U}o40y|XZ$!8 B$ B?O-szxoGC;r7wztB7ka{J'xn$y KCgg6p6ohhepo#Ael'oƂ.0h,05h8o;Xw>8-wAAaWs )ׄgCX{ׅY'j(۷RV5vIPiif Ih8kR`屇7k XiV'1}(xtWiƈgtgȇnHwyyg'b Ke(|ØHXHphJ6d[#d%b=c?a(hLXcxc"5>``(M8aWXhۘX@[(؍iȍǴySy_y͇_R5Ti^z _}{%&wQ)z~)4Y` t`5!dUȐ@bAbGɔڈ4#FMYJQL?SؓbdYȎݴȕpYxj9xʤrwc(xOXVyiVQLA]uKcNu铈^> |-7 4i(~B/ 0ueQ?$YY3Iy^#)tyU əPw9sԜP9 iIٙ9em X} q9sٞ\YI)_I 埧堒Y~yJ)Z?i{L:G >ƩPŢǙ\"IE՚ ::ɒwi^;PDzFH|? G_O~=JUǓ9jz幙djnɡc"!.TJzJi z95S6y41ڧL!:s77j750bV~c3bq Z0jjs n6r%jf++ ;Bsa 7F«ujЁ&Q@!irQx(ޑ!Z3 7es2.%0{ ^s6c%'"4:&sa b'ʯu1_2Pr 73B")b2d.K;"7 "!Dc= 2.+NSRvo-VD -Z7;pCl">qtS6B$CI.%W{Y[/K#:^r'5K1a3$'Ej`s6P k!lk tkv;x;ւ4i˴y&3;;'}'ae;pa./ҵO/ Fӷb;//-Vkz#K[R.蒻"*ӻuѴ+sb(P8E㵈;C/{!Ak综$./>2;".̋{뿩0202R  "6wQ2d *K+ 7+<2v(+?eu3l\kG7(,2P"fZ4~qO"F4(pQϑa4:C 9<{="񫅋F<`}R@ѱA<7I{1#Hf|%46| L<1q-$ylQ`q |K<șh 6 L‚ɒ츚{tȚ kZQx67tcîJ:lӻj9}ˣUCQܽ7ʜ|Όl ͣQqu1R3#q03-S s #m:ڹst-E-i MMJ]Cʠ|:!ya?]ge*QWYm|bl 9߉ЂM\-kMؔ eJʣ#w Xj3PJ0ݣѾS-U:Ҭ5=ڦ]JGʥםԍGف= ½s=͖ؖ@}uܗiՆbpvMwzYFH ߝq]=He; ܊ߎ]{ ܍ӲG۶۩&$!)ṽ~* .9 }-m~ ~ =j[}.nGx63e=c~\)oq.sNqny{|^.>@臎艮nnNnn陮 rwNx^訮ꫮ~.nq.z~^N>Ŏs~+h u^l^q r~Y绾.﷎雀o.Y wrخmo>Zq b`^N/.#Wp Wtcc@1?W)<n^GI~snZ@o[/:SW `_ k pl ok?p0~ y /oZrOypv瑯/u?pPoO_/o辟Oÿ=ǝ6.Z_9S/ޮY /r@K_~!$Z$XPMi#p  B,x@,W(ТB!ETI)Od!1eΤIM9uj1OAZQD.eS馋8*| c,3fi $Iim̵qG*[,]y(QNtq}iknɅ"nzuױzysϡS{y艫?}}SV_}+p#@T@tA pB #lB 3 P>0PCCCKD3$1E\E䰹cowqQH"Ѷ G%[dD)Hϒ*԰K/ E0ǜ˴45l7E4!C<s>ԓO?ܓ>PHUP{tIQ; R*B) DGRO%55KW5UMO5VVKXa5VoS\wRD+W{vP]QMX_4Y[Sc%VYg6n5Qɥ2L0]uqlWDv0^ys>tR_U_{QwuPafȇ,#a1VXc9xbH*X‘M_E.Yޙݖ}Yݟ :Μ)ͣLzͥlz̢'|:̩z'!zÉ]zı/. D0O6>>8B X#`1>L|J;M/*¾k"3# \+an=šl =((ϯ c FuyoyO $>m^3 r7|CNmgHde[LY?\럿M[[T-6JE9Ѐ ߷Emnz;{ g >_)Z/}3<hCY0dJes zb욤Aخ  &=Aw!AFTE"!V\`P W xˢX!zyQA)l"2cV60 )K8IHc&}RG&DJ.uRxl96T(0ZYW~pb%v"Sh̄Ҙd/dGD3MLQu,7uLh̬6[>Pk:sB/!tgs8'<ɫv'AohP0'yP tZOӟӦ̅-s#.rnӜfNʳ n\vJiv!fOY NGFiLy9lC#I71dU[jWU|Uc%kYzV5iek[ֲsk]jWծrk_jVH^~_ {Xbغ:ud9+PV^ `Yжղ%-WG[ZԞ]arY SW`1eتnՒ5QX`8!W[Ur)\Be\p:.VjMP\ pM7jg`lB>eojk6uPVnW"!FA^$i~7;] 9YYxU\epΰ" pJ&?7A>ӎ xBNro&+D) Tβd%c沕Jf-__24^.s39jvsg9llA׹Љ3=#w%ڀs7bxƚ*{:Ԋ1JnL~xV5\Yeo mQkm*,þhUlgߵ7qbZExz؂}o|h;ynw6KM؀ ĴV} ܬ-'{n ; 0pkgn6s;+hƙm<*w7=r,qkV;ц9r3F zjOUD+Ҥ3=c;:ڦ˥ò|Ow$Ru_C]lFPzbt}}|@o~v;?~GҪgM}_{8Y'O-~/'dw]G{L>?s|[)蛊)_ğw/c?b&dp1߼ב>ϦӏGmC.; [<{* ;lt D<˻ L@ǫ?@ ,@|#:ۘ俤bk?A????灿A>#BSB3yAB"bBBR["×B=AK;'$(B)D.>/\B4|>15\6=T8<9Ժ>*? dE AS@JLDl |DKEINOT< $y@L|PW\Y|uǮ aB>:8 x@TMPCUuT?]TPU;l{=mXCQ6|U?4dG̛UEMKէ"U\_$VvdUfG_ C\]?d]VeUqKR1MW20Ku5SRl׸LSUDyLWy5daS75hl=>hMko R`Ä=FiOj=Vk}XXlՈGuՐU]=X .ՎXVXD$Yi\VpٞY5YmX*gFEإMY֌[mdY9"~(Z!XyJ$ү[wM[xD:{W|-[Ot׸u#W%!na*v%1b*+bb/eb+@9b5ށ*V<]7vc/8A.dbC"V$+` Vb5=/bMN&d*J>]L@N&)I^KS~MV e"e^^$GށVceb1La6@ a[dac5fe/Xfnf]^nb>i.&dffVn&^enwEzֱ{紐/s358`hgH@hfhV80 Nfi.6HhF V"i)highg8hiihiN.jF 饆Fn{eII&邾jehFigX>NkkkkkhNh600k4슎h,l†Ffl^#q>lll6`>(8Ohݺhlg/҆FR5`FmHpŞڶVmNn^n:nΞnnn!,h52244M!,==S)*@&'1 stwHA*\ȰÇ#JxŁ3jŏ;I2!ȓK\I%H0ctQM(X N>w*ю4k]1EP#:jéjV]C~ZBc ` [}KWb\mex޿\W`nC Xxn 0W4Tl9C<@U6XLukQ6X`A.؝6࠴ܩwxa! Xμs ,k € ?|tо "T@ޡK_I@\~#݇) :8 I8!Y(ia}{"h%n4)v-Jb82B A@xa.N!,MX22!44M,==S)*@ "#.  H*\ȰÇ#*LE PQcŋ%ipA@8R+[rL9^,0t jϟA*`(ХDN>=TZ[~z]{:Im[L>ܝ<0aw * vk)€ uCϠCMӨS^ͺװc˞Mvsͻ N rΗ_lسk^]gѺE0tϾ33`\z~W8\hs?ՇEy.!,?*!44M22,==S)*@ "#. ' H*\ȰÇ#*L@E PQcŋ%I DiAIZ&@M `ϟ8u h͜By54)ѥ>:U Jk"8EX!G ԱдNxE`k ,@@"L`QPHLşD0Ch@=pϩ_6*ڡ mQ7qEyMϛG깣!,1,!22,|̨**8ghm'(2 H*\ȰÇ#*d@E PQcŋ*H $KT0ʁ,[TS˚6KYϟ@}@(A*ZhJ mZTIR0*֟ZR D U   t`1n٩"0A F* $ g *TB8+ 1ѨSȚְc'Mڵ▭{k9:ց[,yp羁+_{:Ps:o!,('(('%%&')+ - . /24555666555234&C%1L(4P%8S"B_'He'So.Vw.X{0]1d5n2y10;BFLNORZuÝþݾ̶îă^F@@@??@@@@@@@@=v9S5<32222121212121211,,/]&9?A@?=:9;~=|A|GQXc{ps|mj~gc|~asx]zr[oYnYmZmZmZl\mZmZ]TUOyFFlABh?@g==d<=bK@[OA[RAZRAWOARL?NH>J>?<#o?z쫿%{?/uσ9u < 8@\XA b~ p"\ :jk ax@¹rܠ aHvHmtBO|#  #`%xE)G9-1NxpixAFaLhq0suѱo#9!q'c=15l!# IHyqV"XjEQ613e Iԟ9IA|th_2/4`%YKJ>А-H̽Ԥ-%w(>s|pde$UQ yLXqŒ;OdJ3{z2aMz,zw箆G2u eB %*S ?̀0GRz(GЍT\g;ԉ3aJqS}RT$L[ԕE}OҐ.TCmT)=8i5龪Ű*թX:Q0&1E)JwVb5q}f&o353nF2L! a3;_ВC8F-d 'MI Ҙδ7N{ӠCMRԥ5WVZծc Y֯ƵwZ׺5-kaض66Mje3ٝv6iiS6m?^6}q>ӭ]ݸ~7=ol˻޴7oV߳7M-7F8 pO3὆.LS\kHŤ5 +N鑓\'wwʃx&?yI>sN)$M gz>tG:ĕp++sP7xuw_w=zƉ{ƻ~nN{7_:⥠ ~w|xJ?|'}y7=ỹO_zҳ^o%s>}q_^{^1{;>i|G?Ɨ_}['=#o{kW=O~??__w~{{(~ ( xw7RivtGp)xjm-m/ho+3xp5k7is 0 @`u9pEi1xmIoGhMqOiKmQ8iis@xiv!UXiSn_hqcXmegXrihikHmmrlupqHzf~wp{ȄhxHl8qX8Ȉ8nk((XrHȉr 7hmH5077xl0nhH؄p  Pii"0@11 nPn Pi * "( 0iȆpP|Hֈ(v{P0ְi 0 i @0n7qB nA2~ iP 8 Ii p 個H9iߐ (i ް8h)cYgɆ` ȓT9iA9f{yiv{ ӀP68iz9|9$`fMn%sp^(3ə4Ip  Гv ipN 0&9 Ii"&h 9jn9iIi隰9ɛ yvin's$x0pYf i X9ȝ,Pii i)©) lFYIɡ:i9ޘv@[hx 8:|'A (=?z K:Ǡ̈́p dG]F[ꡜ齌_;l'4*lD<  .su9-iɟ ~鱎.38ۋ>>_v <ľ ý-ܷ}=Fƍn來=Y-Nܴ] ܲĭDzxЭ,Nwf<}.0<~3ޞ8I鐞F+X//G_]-`[ENc1o_oGht_v_pz|~y?_??_~_O_zO_ ݐ ?_ƯȟP@ l̟t ݗp @p w{p |oxu?ODО=4Ɖ'>ĘQFuRH#?; ny9)" %}$F`TC;SR"6SU:UF[f= Au|ĖSK*˄q#pͯK^R6˷=X71]}:\56|b 'OSlE {Z4 r)C"Ml$Ɲvnw[%D[Z,B !^\ԙrӟۋ~{˟nx$gХ+<0 =Լ0΋7c3T1n{hL:ȓS9ZP<a;H 8(HC{8LC@`ŎH=daJe)JJ+rK.˼L-rI603: M1SSN4>sP<4tAUN@MGMOJlPL-մSD1KSR95C6ap= :B 1 c/H{{hS6UVANȚ=u]C}c]PG5UmU5\B3LJ:لԹd^1k[EW[ugHQb^ƫVXec$N-.8bDn+0:XPd\eXRARTŵ%_v蠅袍J.*c)F@1ّPjNeq\$>hJ(]1!K15&9|s#Nܺ‰)m(J`\sfuo*x2xߔnfKJO'{o?}7 ~IG/&@e%1PA`%AALЂ`5AvЃE8Bp!4a U/ ]Cְ6a58CrV#h(8#j4BkHAAfskBrYmw[v$, {fL zĽn(:ܮ){Zr=u+q}/mw/~'h1◿B(;X-p`2X~u(*8^/RQDs53BplX#`w5|.YhB Є; Mhc4AH\ N&2$E$37ƽrx-Wf_9[nR&k KMj+mseW<ܹQpJC0n&yJ7v4ײͫsBę!ccu~y'3 B0gkz+l aT׭O-❷@字 `/.Ah. @~`򀾼3? ~;~s?7-SO}==s[ד~ncϼox{ȯ0OCfgc?/:y 1T} Fk{w>]4pZ+Ush@'yl yԽĥlx HZ};S{9˨ @CAsA"8~#l P/ @$ 8;e-aS`f.`N>y5ҾE_\a~.+XF`bKja&ڳ"&4z&%`-`.>.NE+->.N/60>1V9V,V:V`u:F֔"Ca@(;A?8=d\BECcdTFFFdK&8FGd>^dINd<Խb#>?+v+b[bKTV x$ e%^c~ Aaa]f`YDfagNXFhEjEk& Ff4db`^&goeq>fnne^g5 VftgpNgx{}nghhFu.C@[?2~g3g.s^},ǚ,ʛ$ASvdkHBRReBMl%亾"U!S;iQѿd~d6em)3\eIfT jeNL[EףJ/:i+N4eYL[[c;A\5Rm^Fiƽ\\ ޢ^h@'tss) w$r"t ~.:ttoettjf?[r!tUtV? QfZ aYJo]P'u`[Xt^Oq_oa_qe7hgphp[GrlmZnvpqorvaKx1iwwy_wAw !O` z y)xy? KXx( 8pԗ1x/<xy^x iywx=yIyZYgxy?xzXy y_zwKhx(8z9LX Ȍؕi` Y pҚwW? ? |O÷J(}8}}1{\1p hًs ؈߇_ ?~ ׈|G '#yhgz/yJe Пϐn8/+hS7Y(C 'Rh"ƌ͓K*haĔWlicK<9aTY|piː3kbgà Ej)̫ZrQNLFZpĉjRHjXa@63Yc&l shO@40]Ꝉ9# 7g g,Ǒ'R2f͛yn M:ҋ.t3ݼNzbC0GҧS]:tGd;knK~[2m[q?sNWo&WAOfx-uӽؘD3ioN{᧭r''@mp^9?lcݗظ?~~h{'6 B<ր4V7oT#Y<%a $+{(\gF~3{OZ&ƿMٟ E !_) 1A՟b*ZF`R^ `%1@*HP F. rXfC)X)SFX6Y r yΞm  A$v!~j Zrm0!9ϕ㥝ay-]]"a b"""J"$^5%b"f'Rux'!!R!"&&b]!:сb)+" Aa`0`ٚA// c3cX>=߭]4vR7Y1N67 a8z9J8J:U9Y4`;;Vޣ/_=.3#3c?@< #N!N)FC>$D> D$EVE^$FfFn$GZDv$HHdH B$JJJKF~$L$M֤D$NHʤNOV$OPdPQdQRdR.SSF%I$KVUTdSf%WRVv%XW%W%YJYSZP)d[%[QRUeJKޥ_je`%`N~d)bVd XF @dddV(BDf+(ea&N~$+PXgZ$+NhRde^&,B \ B]%DƂA%iFh'M%_qvdq._:S6'tC)8$)TFvR$ \& Cgygg&)C(\gtNh:d%)jV(XCZ(xg Cm%o>d)CJ|$F(G"gr^%zd n(FvjCZ(gFgvVdn:d,(,hm(DB)8d)h)g|aBR$(Y&(, Ap6p(R:)S^hrBXRZqbi:dbEz~gD nfF$ڨ(|%bDn$+B$>d,f%DB|~A6B6BdZ*J>d2~bjjjj꩚**:ƪ)(Ś(Vl^Zsc )Rζ%bV)NN%_f:<^NmRgBJ(ւe@>.zBd::$+{(tPR~d%(~^$h,-JC\-YN@&n Vd f$b mFzFmZ&e^f).%L&聎¥gT>fBhz(ӮdB nUn~hhze7iǯJ/omRpZ?0G(/$NpC0 p o|v0J~00 0 ta`( !C"U+c;( 1ӯCCk>1TM?c~Oq19f{1|1@y1A`W1;11}>18A1S1+3rLyA?B#q;#'2!'.-b2)΢)"+>-nֵ21;B(2*O,g*+rVbs*bb+.13(322Cs,253+'.s+6s,?3..a%0";8vJ21@Z' 1;P{<#<7Aǔ5ބ9!3B:3''tArR@ Es# WI˚FqJ'd*;4A3D4{4(ctGt!4=tɟ=d@P3Q3:'<4K;BEN4M7'UguW T)5@'&,58'eR 4Tu%ڨکy]O{uF5Z'5\k\gRb X E^*u4XTydYRkKY[c3W+ZC%x%9uUbOw%i'kcq X 48 ^&]%7tK5_?w6ǑZ%y\%)mw)7mcwC<$yލ4ws÷8ߝeu%8GلÁ7/mx9a uSs5'%6b72 s52cy$rcY+v]8g3?7k0ŝ8Uލme p1y/_yx4s49y#8&ڏޭ9y&AXyC% )CC%t}{':陞8w8k۷y_R@!8&ҽU1RcR8cRZ<8)u»u]%hÅk __&z8SŹ%%xYi87&{;[O{']{ou;1_^5@<@8%75&e7@tec06c|zs|``͵^;/|w'I8?6s'/4ù>~?>7OW?p6/>kc>VP~ۛ製臾?|>ۛ>4@ 8`A&TaCF8bŅ-fԸQ"FAdɎ&Q|e#])̚73ĹӢN?:tPCTNMo>JSԘUĚUV(~-VlHe?EQڜnE**]mFě^8(ᔃ SbA!Gp+Gr9\/ud8vW?;yذ!x]RxQg-W_ט\W\c%\55aaq&n9K 6<1 *(ORP!i^j[U"ybB@ KcJi=4`d@AePZV(eFM9.Q@"JD:xY [ViRȤ<&i3C/>܈1d=Lp"n%22!r!GG80? dc2I,DFcK9rJUKU&;rziKӅČ1/ˋ!Ul3W(,55MXi_d 5os Gq>l;O_f_ |1H? } 45A PsfiMʖrD(_.eeIEAH4 F:*eniZ^ebiS>:baL f5\345n3?|;g9vl@/6<$vhh&҂4/BDiQt`;MO5ʹZ68ӣkQºs+!JI-z~u bbPG]MhjvZV.m hcϺh ^&H1H!R\U50'| 0s5L{8݀">m/|E!xu;~`s!EqN7q\&gxm^%x7sؼnHkP۫븇t: @XC[F:Cp[:N U!a1 _}[;lDu`7gy?ޛw#~?]lxr3~:B̚O -r'0 0n %B/B?s*'2" ? rjqQ !q.2a@\r An!Y* Fj4" )]9GqAoCC/ LEU ZEBXp*XM VAʊ<Z.Rʡ`ƴ DDԳ3?SH4N.D9sҴMbOF׌KK3LcQMkMO)0'!'O.SPSSST RRFJUMUV?//JV{TyPW'WW_cUXgUVtSDJ̰Vy EQY5uM@xjuX4X[XZmnXWOYU í[JҕX5\U_u\J^u^qt8[`S6saٓ;7o::7Pa4yaU;`iScc'v=)7Ebc4-VbQc[diV67 `ϕ[_U]s]wh# g}tV\w6ivigX쐎Yij_Vk_{kl+]1Z3^7m%l mT5v涬ln^k6liVhUmwp{m?qqqϖl2Ũ*pOVpەpUr/rr >ktM7uVsdgWv7v[Wlk\mJpYWmxЭ1/`WfeK6am3e#f7 y7ccvcg`fk{?z76wdxF}6e6}_~z=i0yGxwo7psDw @@Sxq=7U80,؀#x8T;xT?XZvgkԄ5qUm[m_8QxUYxֆ6[UWojʇ K"svu؀{w Wr 6tk}gsX Nҋ8x㘌 7Hb)،'׊7 bӀ}}^y祑YW8Žyc|"YwCYw}-N?췓wc[ٕIyAeז=~7U`7dɗ{{y_Ye78s-9 X) Ҙ9فJ zsYyXXypL읓؟X؍ 88=osXEz--Z0z9=A:Ez;ZIQ:UMz]a[:iizm:uZw}qz:z:{{᪵zګzZ:zڧ.ң2AW>Z@OAXmZ=Z:Cڣ -:OlA9ZCc-Z*(a+}d9M`*M{1z[W)gQ[BuS{9]`[ݺ+2!<@C,۲_^ @*ZTA{KL6*LoN ۤ^ em?pAM~]^ /~^^}w>O-߾꡽=AA>>1z^#C\]>I۞M?%/'>׾mok_y}Qi_5:+왿[%7u-z,!{7~-:{a/_}~=п_m! <0BAY T/Kp*y(ԥ[[HA[.<%Chc/z) ɔ<{\ "Ơ!nWȑ%}:}Jp%ԩTj5֬XzzJO4@,j-J`-TT8=x˒ę 8!n۷0p NE^,6!ߖAU&0`BY`Z.tj%t {lڠ{ Tܨ{؜=vۻKM'd.t&ٰ RXQeqrB 1̗ w!ADqP tK P*U`E|3%Qf ? GEx4', Ej8_hB.(p8ъ-Xѐ16cIݖ^ve`)TxBtAD4M9"ai'xxRI8Iܞ} gYڸ5hcFݤ:vzgyZ)()VW!*:լF5lbwSl:@N+ ^mnm~Kn+. o+o{onK[ s_Pgw ,F*Ӗl0Ǭ4k|q<>DL[1 -5ULf5] mo*KKdj3q˽3-7p뭵觗>:Ҫ9谓鱳N;zG {3_C|goc>௮=ʛ}/ۗ?0 `-@ 'A*" O(=o KpF0P|x4 MA#όh2m<,ZمDEfSYcvEeqd[Xp6 |DŎQcksbsZwxGQ{V7G|rk$d!vHt%_Ȩ5[$)yIe[ &NbO# I)JrlL\ U_,oJMe(}Z-.idl$ʤ"fTf0Ia &. g5k2S&7 Djӛskg7HzKu FCL;yPsjCC~rR($ e=.P1(I;Ž']I%”n4~((mS4)LgzӖ+maJ_ZA5!)PSBʳCPo[iM1Jը~գFYezFkPDh{"T ][vI%2)` $)J \V?XbuEEkRǺ)>Yu*UCo,Բδ qYOOmrJc0q7h 8],sy^Z #z 71.6yr>o#-6Z"b[g7os{hF/46L-L,iPS&u1|zov[j?:ңnODp AӻVpU]\MT>b6Ѽv6]4@9҄&- hB㠖l0 w *P H8{t"5Հqhn|1w]!FxO~NhЕ O(y(0+ .~{GD0tz?`)R`t;$2;1 ?<[(s& >20gl4tGր9NЏhyDދҩ%;^oN.S '<wmMnu^;3Y@ Oч-O)oz=j@%iپ IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7zbhGK֢MjWֺUicKE=vŭpMiָ΍tS ZWսvzxs;VMzwk醷E{K_̷/~ͫP+a/]ep,aVsgkS@ÇSk nt-U5 jx7flP 0V-P2<&FhnBGIq-c1 f~nk2LA]@9JF5Dg>9ABF0@kh^ ANmg>ȆhdNyjlDoͦ-üTE5Y+YzStkj 6D``hdf=mmkF-iF0u g͂'l 2fqG>| "'-Qnc|浐9srg8w49ˏtD}1ϣ! 3^zye_nv]gmڝ{pw=]o kዮx|Bw| ?Z7:AÛ{aO]vOw[{[ٖ8MZ0 :ACF{,fp[Z0oܖZڧng[G~ `j䥁~~e@l7Z\hw^RzƁdFiiec '$(Z0O `qG5Ȅ~@H~NX\UZ8n&hpc$&g bbi0hp7WZEDvhZcQVZdX |eje !um膋ȈH]XZh~HHZ[x\kjj}Չ^H8x8ȋPH[؊fhfXa8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8:<ٓ>@B9DYFyHJLٔNPR9TYVyXZ\ٕ^`b9dYfyhjlٖnpr9tYvyxz|ٗ~9Yy٘9Yyٙ9Yyٚ d ΐP (NFIʙ͙QǩɜyQ ` Nz@ Н߹k  @k@ 1 Yٞyi )YG )9Q NZߐٛ99,ZYv@ɢڛY0Z (JQ9i Y99Ǜk` ٠*dLZ v Q iYy))zUj^P*QJ*(WUZϹX*u*)ʧ{cjb e z:WڧyD:Y ,>y٣/z :a:Lz<QZ Ǟpi 4 ʛ J*QJ J*RyWv[!,7%$$##"""#$%''''''((((((()) * * + - . . /1455555531//. ".$(,)--+0-,1-.3-39.Y/8P,6P,6P,6P+5N*4L'1I$.H$+G(F(F'E'E'E'E'E'E'E'E'D'C(D(E'E'G'K'M+N3P8R=VC[G_EaCc Hd%He)Ge+Fd1Cc9A`A@]E?[E?[C?\A?^>>`<:骋^yyL/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H28 pA5S@ A؍03td, c!ȇ>1N/& Zb/O|B@$< DDJO 3@3fY $!HH $p1K] ,d #L,=@; < GW@@8 t$ L@ @|’q/xH+"W  hE/QF?͉Nu B>hu0Y yKA lA~7@  3 E+AЁȡn@>:Y NTjbU+ fd}`K%nSPt,hX (a$ dj7`ա%HlkҶ*]]qnUU,h60N ׸Ѐ#[zP԰e#PȧƱq^g) o $V#hG!a/X{$ 70+"P85leAËV7@"Q⻛Ax7X/z٠^M7A=pwj10Y*u0Tف`Ԏ&ɚQu HpK)۶ ] ?@¼.U#J3U40LGHT̤5S+# Dh on$ -Wނ },Cm?3yAMpr`^,a:D8K jᎺ%2)]`$^+KFnUX*I_,h=VQҔfH'nq5N@,nv! YæA,q8|1,qe+LeEKrp4:^TSZn7R`+A@~&G\ xțxz*\Ч~~ :Zz* :zʨکZzzZک:ګ:Z:zΚ:ʪA GX@Ezj⪪Zz:꺮X 9ЧR ` * : T ~ Cj}J ;ۧ[ڮ }x  z~J0 \KЧG6[ <{>BD[ڧ J 9 : ЬЧ@}Z  RЧQ{X`X Zۧ\ [۵ {H <дTK } sۧ\P@Z|\p:{ۺwv{˹K@ekli;}0_e+0mK{*Z 2J@D ;T+˪ˮ+[ OЬ K ;K}Z k[XL;~jY |H}*۽J@@"L`#<%|,}+<&\î@(\ 9|(%8/LA\0J,@NN~k@~*,0352~:&HJNQSU~P~Bw\NcengikNm^Yq^^GBN,Cq,=ig.eNcn\tpL~vE9_3*}^G'>'>^{VWpE~몮븾qĞ>0ծ~N~ڮ춎.^Ю؞wn}>Nۗ^n /  o_ ?_/%K^)`~/_1?3579;=]TUbOMLQ/IO>nG|P]PqX/acOe/?i_k?moOguwy߂{r}/>vG፯/Vox_/(o? ^{:i212GȦh{ԖeKKj]lI!~Z`0NXRh*Svv!_ ɛ=lSO0M!?̽hg舍e\'XpT'+ DPB >QD-^ĘQF=~RH"DRJ%].!+ c'wE vi0%68h2J'/>5EŚUV]~VXeep(wDl,$J#}"LqUREXbƍ?#u (K0p"d/]W|$ZjZҒU-mƝ[n޽ruz pKPw}_Ǟ]vݽ_g͟G^zݿ_|ǟ_~0@$@D0Adzrж>!o0C 7p>$DpRHL ` R1Fg!DFqƐVi "D2I%c rDQ,\ 6X޺S/$sL2rd&s{3JOG 2͋V$ z0%B 7D3)'r9QI'RI)M3RO#崠Q1=Fd1>W5VYkD6N)U"W`{%Xc 8sucX,Yk֯[w|33[q%57]ue]w߅7^y祷^{7_}#o=;Xݏ#]gayx9V(dbQ|6y.F\ٸcCS^acq&  *d1hLFi/c٘^ N{.bNi檩V[ Znc9$]jL!dX a n#XpebnyaV -Y wX "ͮxݕdeLwu Z:ZYI]wգЏ?(&zo^<ϧ !`ޙ'-/اO{.fdAj6ߡ5 $-N^f $|ɋh8u\Ai1A(!uJm-L!BbDaApqdQb`A C,"#+hB(PB9ґ|rrp ǁ0pG:adj2Nx5sXGdG`шoHGD#It1db|A=0-W $2Mk)͗ʺDғC8Y۶`>c#Ԋ(FAʚƲdxT&q2fq(EQ4J'C"{MgSҴ5ɒ\fN)g3"_YFej'+ JΞ oxy }􏑺ay{S~8mOs< yfӾ+'Govw~ 9bh }-mW}_Qa5Z :[ ˹dCs=?  D  4$4DTdtF2b/.iDZh#%dB ωё%) Ոu&B )(#Ba+A 6E1|KH2s«`C7$Õ荁 (w&6x(@bA.,L<0\ j.ȫ uA,MD- O*ӸB-d4Wn(TA0["/鸯acDFXddftghijklmFpA3S4qloSpxGry8øD=Gj-9Ĕ8CLH8\EHčCGsaGأɞĵ>Ճ>, HD1GnC@IĤʨ̿ʪʫʬʭʮ.˰˰t4˳D˴T˵d˶tK,˷˹˺˸˼˽|K˿̿$KDTTDLtLȔˈhK`]LK KL4MTMLM. <|MMLڬMMMŔ#K$ MĂ K)Y[Hg,8I8JK*ЇMγ<| dN}Nă*ϱKbP `HK)h |O)8 N K}OOMNuPUϰ[8P,P L \KQN\KP ]Q=R,OD ֱ5K75LV LpM`=zE QK}W]g[0qT.PXXW=հ$4KˊXXDӍˎND|W%Y^KGXW4y ٗYTրهرYkK]Tn̲ٷׄ]MҰtW.lYZXN4ZNJӞϲZZPNNOMԯɇP$Z[ mQ , ZPU\KDžZ[u]t7܃̧=ڲKuV=5[m][L]=KMW]WڭEԅP`0W݄R*PP*^Pނ%^ e޳t^_D[Yu^_5=]^=8T\5E__eDž^}] -6a }[EK_^a `>FZ}\^J@ fa^'a`E"\&[^'.cNNe4/N13fbb >KXK$.ٻk4쮎m\idn %Xmvcݔ\٬LM>\n,n nnLnnnf$nVol~oooooopp.p?No_p~ppp$pp po/o_ '7'q}Cg_qwO6gqXr !q'#q$Wr4%r|(r+wr,r7-.r%0?1'!73qЂ 6q54s;?AoB7$Ot gs69E'rDJK, NrPgtQOF_GrRsVouWsXuYsZu[t\u]?qTU^'tawqSucwudgue'ufug?ubtv[aIvw0prwtwhqWrWwy'tL1p:m nswzw{w|Ox}w_~wӺwxLAxgwx_hxz7?ywvwyxgywyOww_xy_yxOzx_wyzoyWІgwjHNM{LJ{IED{C|5w7kti7tssɿsʧs'|Wt͗|)SĒ3/}2?0O}/_-o}-},:|'o|ݷtOq}t׿ٯrr7'P~[~~+`~gwW/9_rgR/sI?Gs,h „ 2lHĈ'Rh"ƌ7r#Ȑ"G,i$ʔ*Wl%̘2gҬi&Μ:w'РB-j(RnJiߨQS*֬ZrkaƎq/&0f"|E`ɟ?K`¤$ z}.l0Ċ⌦"˺!'ҡFAn5زgt,k-]duF䊖@d#ӏÍjbDt LbH5-~aISE銱>r,@DXUIv$M,ixԥ2I*թ>w*VJ5M^Ҿ!ֱf5+XӪVnZj6"E[V ~2WV"a#j~c;XDv/Dd͊z+@K=-cKKԒ-EP׮ -lzlnm+[V=Tp7ֲu%^w*%nq\Gk\&+,^ `e{v}"6_"W+nD_ P Ɔ `,paPoD!o^7]nwEKb>DZ|o [COW}qsc6-bHފom +vciEuŻZzb&/3[Y.DX29Rn\=PwsAOY E3ю> HK:P71ps;KZ3ҝ.~HZi͠tc) "-QshǑֹӦHg::.Q%1ʠ}0!g5oH[f0 Qf}LEd5T"{խ=00 yPl[0y:m3pg-xAk{>q܏/}Tϐ8{8LyĔ`j4ʀrNpU#&G5Yśs%TFdc"QYG6uAy:Nȁ$gBѹyG|r,)` B`EB&y8.bV7]!AG*nq{d!&E(-]4D Hb(A As A>S_,zW>-A$2Vv8.PiDvA&ĎPC R׬dueIΣT+ddž_? "M$QLƴ 0`J䑹 mL"=jLLǸ f` Jb ,` XJ a  !` K ` aD v >a18 vLba $bW j LD!!(b " L! 4*1 %N `"p aaS+KrС(6 Ң,a͢EۄṸ+a1~ a."b`R#0V &`)*F|#8#99#::p;#<+#=֣=#>>#?<#@@d@#A$B&d?B6C6$C>DNBRE^$>F$FnEj$G~Cz$HBv>$I$@Jd>K$=¤L$ФM$NdKd0.$I$P HQ^dx#նcR%RF%GN%UbU^eGjMf%WJ?v;JA0Ȃ-ģY$E%[C\~MrA\ԫ;%Z%[\%dA&E&]r40B#E%\#?$PR ;W*Bj"$k&A&l lJQc0H;@P.Bd# C-f<:B]:#r:gs~?@[1&p 'S'rsrsc%|&TBtr?%1 g=.#.er.gtv#pAty ehir_&ndm>(fn6h=>(ƣA-'n%Hoc&;AbrAh;R;^hn;zhwrgtVaj<~@'=ƣyFr=e%܄dRNbZiHbCVX>rA.;)ph't(rh<;RA'C'dB<)@bXΨ);6&A"+F& eZ+Jvk^+b+겚*Nkk~kr+«"+k+V;iE.)z++N眶iÎÚ()D$R֫VȮ%agYNlʾ,v#\*,^&$FiNBh6.c>r&D~-hZf֦)=h~0 ܮ-ئ$U(Ai.AV-n.n"m*l^m<^0;)<yf^nm0`'J.֣igyv(}Z{0cCڨ&gJjGPcS.T .o"o*@2.lX$C-C2Ⱥ%^$lb^>VcÎ,t?flb~;Eң4.;o]G$6od&Ld"o?ޚd6s0{Kp=b0?p o K Sh o n 00{p pnp0vDChcFO1J_1gk1wC[qñq7f۱Cб1 2!۱!'"1#7?2${GD A8$$ow'1(r2)))+?qGX D@d&r2// #s/s$#s2#37s%2@c353gpo737s65/G@| xt 232ӳ1310s:{(3%t&Š$bDH¸CCCtDL5 ǖYɕhAS߉99ǹ9׹9繞﹜5,zV)'zS:)SQ?zvD|ޤS:C;b*g ׌zw:GM F]Yr:׺:纮::S;{mC= AάZZ NF(έQ;D0;UĨ [EѢ+dd۵囶޵SLTQOۦĻUgʬF^< 9iEd-C)|êH_DD]߷D[ImDJ_d>FGGs_O?W_?go?w'z?k C0c 2q#fVU|D`=@UA "L8A!F8bE1fԸcGA9dI'QTrB6|!M7qIS&D3k]f>  r'Ә> %QWfպkW_v}Zq,СRφU6bՌnuEH e ܾP"[paÇ'VkYrʘ2|z]z pWt9w'D6ztSfue۷qֽwؤO^-vg<#ȝ>>|zqܞ * qn93?|y?7 8fշ{nЮ襈Lp ,>Dp9&o$M j0, ^*+r**I,Q|;Ftl;{QD;aQy R!,#LR%l'R)+R-/ S1,L j:7Sʴ+S=S$R@JA? -CLMmQ.TI)TGTP<1S&- UOQRQMUU 36YUYie,5ѯ]y_ Va-cMVemgVikVmCzաE Wq|U um7oMuWy-[jR})-}8T_?LX}x4xT$>+-K{uNhD.u2wAYneFA_qYy矁Z衉.h# IZ饕v駡Zꩩꫣv뭹kZ:Nnmߎ[ٞ[o3 z!/dBi/~Isb0m|SQz,/9em`۴`D*Ԡ ^0i HBҒB$ᆒKZ.NXZ,PjĨE!@ .%N\6Ī LkbҞؾ*~Ї_!zP '$bҌD%>틅c ch\X8&qJG+*F%r^t"H2jo$yRkb#хJ" L3b\)t R1_(DUiFh$ѥ4M j!6F)JcYQ';hN V*iDJh!"72=@ऎG4CpmH;qͲOHCBvJA"{قdlg7[Y_&~=C[յ3L3icA Rvig)kz5MHBK{nq[vE.r1Zs.ͪHN27-w ^rTmpx]f׵mnǻ\׳aX']zWo»i/mxb F#\Y 70_AJҚ h׎p/Ѱ-{K.L0ʴN`򵯝+;NjC!Rћ~9yIWWX]9iY j8ci&ٽP궲晬|`ȧ.\-oxǤ= `ǎ81I:o`£d^ڝ& )?9QIStNU9O'dגFu!T}t}uf-lغmY+I"qu`tIjh| yk=fIixN$=oQ:w!~*">{G*U9G<8q[vIKqlqZ(FQJ}SչGc=vVw׽ v}dٟV˪\l۵ wltwjW> n/?)~Ϻ~xw}ѭ<NJyj~E ha})Qzկ7a{^aq{}|7|>w|^#oo~~͟}gN B?o/o 0sop4b"Ppc=@0YoIPNPL0ɏo]S",!0pbsPwz` Bu/ SP#<3PBp00p P3"?>Mch9V#&P6"8 N#5P4  dp PPQ0:ְeB];C=qd*"OKq7GQ_q?  1scoqiQk[3Q}Q1qI11q1m{1Q1DZ5 0 p g/ O!IP!C!=!7"#P""h q #e"$ P$ $$%/#oݨC0N %[O%'O'ُ''&EP(o(Z(QP)Q()*R**k*)+w+m,R,5,ɲ,,9)/-p-&r.2/+.rR2-s+.hs111$C2-2 (03AOC4MN Os58fYPWXs6}5#Y<6y7"4}8I.!<8993:s:::3;s;;;:=@QT3<S$ud>3EY8s?TGX1:`e@T@_e \8tA%t> tsB14C5tC9C=CA4DEtDIDMDQ4EunT9"N^W8@rGta bxd!VvA"! NII"> 4 44 D J?! bKKp!Rtn`"8@t"HJz0!Ky#37FUx X`xfd!M E! 2!d\T$`J] ޠ "! >A\!P >TSuU[U[pu5"d ! $ a b2u7ҥOTf"b&H nP#ؠ  RI"̠ @T!S Bbu]Z#x"[!7ХY{z" dE!@[KB\UT7!* O1K`5Uaa 4I7bR&uW64Rva vBZ%Ez"u Ch-u 55&6!U>iYbCfVyV &X@xv.~66&PYcY_Vtfbm &aW4\õU\ U "8!WcY;qn"]mvVVw dA` Bn qK pOUTSWTLo#uZ ^V'vZ @D 40r_&wVn4R!P bz`!t6`X/ut4JյJ4K̀KS}gZbMV#bk#!2ul y!EGV1KS8x!8%xG(+.Crl(Cl$Dx,FŃEJ؂qcYB>XDAb=~-\ZhOe'C7ePh$DXW#l*cBejĉO62aCX4C R$gѸ)~ZBFl#&@AF:9DŽx5 XFD鲌u8X(*@8v={3 B YNj?c=qx+$%Åu>c7;yOb)x#2W[ؗg]^y9y-BS(Mdn`R(bbH{O ךFsr]GTHe-MJJ xpKKI"@ pUQy`(Bw]*Q#ut5@ny;V P! ~RwhIb=TUTMUUumVD=%b]Nz"|!bCSqmwsdڥ/{?Bb]oU !^7IzZRs'Y d[Zc#f ` 5[O8!8u\bյS;c$Mb<9u nw6R!kY9kh5a 0I'3ff#fsvR 6lـh#B{'6vii66&$"V:"b~%qa9sm֩} zaSZ$Pu !;JA<1WQ_䗤u 6xw B3ws!BxY$LT[5Teuc׫?u-J" ̺a{TR{K[; z:R NJS?Ÿ7}?A}|Wȗ􀍼ɝɡ<ʥ|ʩʭ|BYZVMoF8#m8=;xMbbD!,22!RkH* :lE3j\(0Ë 'PɂLI1",UJbK)IY̘0i(H@ % .ZZiԧ&!Jb@!,c''''''''''')+,- . 03555550,,-.156555=!A%C&D'E'E'E'E(E"*E&)D))D*(C*(C*)C(,D'-D$,E *F'G'I'L'M'M'M'L'J'I'F(F(F(F(F(E(E(E *A*19+14,12,11+23*4:*5C*6K+5O,5P)6Q#8S;V=[=_<`=a#?a(?a.?c6>d;;c;f<_A@_DDdHFgKHePLcROaSR]OQVMOQHOJEOEDN@BND:PM1V[,[i)_s'iu(pz,p3l7j7j6k4n9s<|@{D|KOQQQMKKMNQQQQQQQQPD85a2222121212122222222222222222:6@9xftrktalaibjdlfizfdplWhmMdmGdnBgs@<<=ABDFIKHC@@??@@@?@@@BFKTg{¸òƴC H*\ȰÇ#JHŋ3jȱǏ RRHdȉ%GLɲe˓0cʜI͛8sɳϟ@Eth`H`dс,E0)FC. ʵׯ`ÊKٳn 2@JG J Tv-֨jnջPe]V[/ǐ#KL˘+"޺6bVTߺZckx渙c˞M۸sGns.^Ѻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-Dm51>tptO%lYXA1u[3^[֙i-mTb}-Y ,=uNWa_mۜNRq8}=Ռ7N~`KNys;~7ޗ5s9懇>9mw雇L]:׶?9i8ᙳ6䳯7w} ^?nYRtSc~=T@_>@~ᯀ_?z:)@!@Otg^ժVbR~6&цP5gp'ӧo~AR nk|[XFx{T y؃ெ5r]`d2@q dR d/\KMC;ѐ'2ARF~8X1z4#2}iM'Đqx &K#K!QԵ!Ex+؁_7@1u(B58D n!ޯEHDlK+I7!8>wdSHpS7;H6 *{6p[$s.κL=}w }k{(޶Zq4 Mۅǖ! W3k}7…iul[)zO[Xos=^NhOpw;N]s{O/C[핿͞{>{>ðE0Loҳ}c/{?D ZV|=/4~"X~~+ˮ:o囿[A0:9^؂2 4 2 vuR}0|!l[ 0x~yuwo"y[ P}@}B  {Š vxP~u [|s7 Mu.Xv205u7u9(Kxp$XivM 1(v [ ا8BQ(f|x'p{H7tywXyxvbvrP b7 }~` Ev[v;(\'ewu؈(((~x|-mȈ؉]d|wܰuO x~~5苊'ׇv`^Wuzuu(B~Р\uXԈĈv2~߸uh@j(gpp[v؏uPP )Au ) Y y(yI )Y#!ْ2+4y$8ɒ(9: v>ɓ:uAY506BWHyDٓRɑT9VɔKٔFy~}uȂB@2ō؈uvPiSQ)&MPՇK2 Chqɕyiٕ@y]s锔)h {9\g}IIIA>A@[[ >{ø8+븓 [K۸{[븒{˺[ 뺲 ;ۻ{+[˼;{÷TF?뻶+ 㫼KA|˽K+[;ߛ ً@pܾ;[])Te{q̲ܱ,!lI ![<5 ùKk q4*/ij$2K Ż*JUlHs:MLG>ce\g,i|:`Tarܮw̮y{̭m|tlLp,ł| o|QȉJ~<ɘLȖ,Yɠf#`I/'bKKKp TbJRL!@ Ū|"eIfP uQ0QeQBk i<#eTs_XOucdv!\F`LȋEXcEc=SuuWX%YuS&l,鼋U_UXu\\΅U EC" ^1`uOA{aq"-&/^a5V`7cl;^+"g i6eUefetC.T]V}XZM3R"oEN$R5BF "m`^d="7&E l o-"j0Tsb@-5[|؁e؊Wzmn,GsUk ؎؞I0ڡ-"p'|!B+y "76!׽o="s-lm!"֜!e3jvt.=]}m2ˡ!ޗxEeJ4J~7ZL|_aJ eW-K} }L:uM] mJ]%qc que= RރL4R~eN N  `aJaLaE =iNT%--]O*V0DjvY>\_vfC\@>O[搱2&qqQNmn | ` P>O`S%x:&lб^fMn>WW]N_~KhNzc n^G_ Ϸ^n^쩞TҮ B Lꖁ j1Tp6E8pߊPUcq ^Ia\J!.0b? :U%dld7.`d^iZ`7E)bp?Ͽ> VqX,/.Dӱ~CU5]%jSj=FM<-JAHGPZa\Ε`b_dfjSU_H_\O_[^U8^XW v_v IG[_UY+nPBtK 4e!]/n`^߾Vf?_4uOvn??c n vבVa1mPnb6-n q+T(\s @@V 3O -^ĘQPAp S@+*d0D9ęӢGD$x2%90 aPu>q5Ң^*=PTe+u(B^&UYuP8̠H/߂%@6>Y,}ؽYsN%ˠ}N [zv0B<(]u lm(ҦN`Ϯm.jAͻi/}m=7wOީ:ɻ-g?=uqCm<5 ,?d0[ ʩzʩ`mߎmFHonn {g+q%7N1G|q3/W 5CO?_R >_eXf!ݽ㫽^:3&^'UÞ1Wz!WoAvٛdWggYwN/>DzQğHgg?j̗`f@ͬ{J /OT(\p0ol(CAv+T-)ZȸBx+jb_x,|s7m!s g0˹t3=muǢ A,`|f]X0dp>4~a@8ƌv4%M7!;kqJ=1F.ms< 70Jͳt!hPDZ!znb !]>#EE{QԅEGwB~xqxXWޙͿ'2j83׻;]<ĭ8 EinbAh!~ĸOAlxp䛰ԧ=GkGmlH|HvH_dHGjDɐEIƖE ԰V|C4D4ɔ|ɧPD!؆kDc8;1S+3c@RCkD1ܾ Kt`CK\2lˋL0ʿ14]@M,Ķ;6CC@@K٬?K,|M ,΀KB;ͺKNE >N! 4$cNLL4=GM|1L:LҬ[lN3n1?E1]]HπKN7>PP 89(=!t CeCQXDDJQQ 7u3-uQ!"M PKd1' 2.RDQC +-3?m PPh|su ELȲ394TB1D P;TEu1CFG=2FTI TK =LTM1TNQ 3SݱREU>[NmUW}USUSTXWVUUTUZUYD-02[=I]VemVg}%VRViVVlV@.9opq%WK)sEtUWo]vuw=xy=W{x}W{uW؁EW%؃=.nVE؆E.u؈5Wn؊؋؆؍=؎؁ِב%YyՉX0|PYv=Y5٘יyٛלـYٟUנڂ-ڒ͉57PZ89Z%ڧרpڪ]Xثڄ~}گMYHaZ} ۚM[|۵U!YYEY[Y-ײ]YM׶u[][M[ [%[]WZ-Z]ڦ}BC)/-/έܵ =-uZ}=]%٭\kQ]a]q]ɐP0]ܭ]%=]ޝ}޽9M^۵^UEʝǹ\^^m]E^]}^eeu^%__U_]6u^}ݕ`u`_`  V5[eܡM]am}aᩝaaa2EZX b!Z"!#>b$%^b$f@ n oz[&vZ+[+\b cU!+N2Z6Y7Y8Y9Y:6c%>M*fb;Y?>Y@&YAYBXCXD؜WEXGXH=Vc,䌥KvXMF5PcNZQQcSUfVvev}WeR$)+GˆH$$JP+6(g/g:P%V)Y%[J)Ɉgq^p)YhyBf,oxr(w'ybPȧ}ꧡƗ|6*s(2**ꨏ )Vg6iȒHg蚚hmNYgq>>X*j*R)?A֫Hذ$ޢX&FVf.v^*xXn(ilCC$^jGi Ӛ-CBlΈƆ#:"璯AkpV0VhW'!m!0BZfnp_։ݦ/X66d~n2 yӚ/8Ҋ,& 6Vfv+4oop|Ԯ+^bG7Pbo֐$l2hFp]` a98c(+&Hc6^_ 9 G+<5pFpobvdx^dbX2;fifg怾rs``&8:z\ЅЅ)X% OVx:BZrobCr~hwrhXvg~xPW /lj;&>*t_@*ggT/h<T獈it]u{N'`Whq #F bGvBcv)bXYo 7@w4P`_ ɠhp䀉h Pw*N %(/_z{ hDE{,ЁزK/=7*$.բذȗ3\bU^$.[+`?v ƹ{׳Wi}xtɩ6'&t:}=7)O@M(dPNx j!ou}ء$Z"Y"FQ8#1x#9#=A#Ey$I*$JB:QJ9%UZy%[O:%]z%a9&ey&! 2ik 'uV+ی+̃@s@S 4i!@bHAxMZ"shAX3'R*#Jɪg&(餉 %<+|AȄ 6ŠΆr VK+j((R";nol 6Bkk*چH,+g*+D@r@8A{6!l+ +1}0:Кt1Doe)6s*N2Ɗn1;Q#226']PӱI5a uQAC 6Gts+Jڪ}7~3lxw{يv{ߊȫ N6{ |; լm24 ᮴!/.إxPym ~vޱ$b}Op._{.6%l8':'$5@Ƿ'[ |EܿAmJ>'A,pn s7?2@ǯkլOkBt+R~dF.P38X@2c0}RXйJRFPɩN|"J)RV"-r^"(1f<#4Nkl#U(&RNT1~PyJ Qe#Ccz F&zY9b1|*IKl yG(tN(Sr=&4GEwBC2r~:YQ',ZQf:&4KNڇ_41bS-CXa#9'j.#tZf5|oI՜-jY2/)RʣJV*Vխr^*X&!f-k(Ϊֵn}+\*Wuv+^z׺굯~\ v-,bX2n=c#XJe3؇]= ѾU=ZM A=k_;Zfik[f6m*Vh-o}\*wms']Bwk26 6aYxj^Zi : xٺp Z F/lVAÐAy_!HaxDܲ"du+`Vw-u,{[a4rn UϊdlݺڂrHFs A6+Pd!B[`ȁZw B&ad)p'Ǹ.sg\AGτnVxn0F^!7^ @V-XtL&ѐtY+}i~Z沒0sm35Pv~+n 6EيMB{Վ6_ G  so곶Z kϽQOvm)cZ&EM*VwYYgs, beSf:(`\Pxe%@Fs|PE38mr|5/yK~|2Ѝs|ByЋ7=I:Н.\ן[[zN\i]<v}po;M&7Y#-1n|NY>w۝耯: Oԋϵ7>Z׻aw82a07ԛ첶BB`l3vOh zgU:nBɯBA؇Ƥm\p{ӭβP_uYo6 ش]`ZƘC% ]`A1DYNh qY[- Cm Ҁ!x1CD76_5!!a)1a׃,.$ñ؃ԞR¬q |I|X^aeayuaY _邃`[u׃#$D b!!""[iAb#U"Cb%.&rb'j'V((U))"Z*g%*.Z,,+`-a. .b*+"-r0# /"2j,JH6M#5V5R4^6n#7R7~#8vc6#96#:7#;8#:ClI>#5ޣ?$~$AcA BB$@6d<>$DDNd8B++<CZd?V$HH$9I#J8d>KcL#L%W2WdX~WeYY~eZrZUNrO$T%TeSReR[eK fW=$NIp7ehMAFeVdeR@$Ffnfvce:g eigj&jbkfllNm&mhi&lm~jm>qlfq2gls qgmNr6o^guFgpF&w'xf'n&ugxszJgij{'r'uvbz~@dΦ=S}vxgzZ~'RFf&(iho'z}Nw"'S煶f'Χ j:DNklae^#`eddN[҄G>+h4 i5hJI"H*)H2E:i4>DH hz*e阒iș B$4iibiȝiri@.j**d"d:j jTi>^*))~r*idX*ƪ**`t@> Z2y+E!>IH HBvI)۵DB9mI&۸Ew+kE+bHH >C ԫ+,,&.,6>,FN,H(IT,v,`e4ǎ,ɾPɦʪb,&EU,A@8+`<Q9Im[(C">Q@>d$^-fn-v~-؆؎-ٖٞ-ڶm 7B ƒ.́@p@@.8/@A#<0B mXHB*.Ad>&A+@A߆B݆B/(@`@1m(.@tXb @nDO(xD b"nBZ @AzG2G0| @  X2/:m0D/ADo.p(p%0D/AzR{oJnV ./ J.4|A0Z0p(8 ;CA0 %`wp% SH+An`/GDDV+ D0nJ C@cq51NMH[D p1kĀ7 A D p 3pB";r(T;rEl"32RR(@f^. 0-"@oA4#?k$?$-3r0?* *Ar(r-s(q <4ϰ -̡- C醂?H/J SB:2B1 /:3;O6CC2 A34޳@t:BҰ;7C?4DGDO4EWE_4FgtPFwUݫ@3E1 y "-ӊq㨴,&9owklV BʹKtTR+5TLt+%M$GQqT4PMUR/ q/ QGS DX 5WWK3euQU8AXCfiF=l;5W@u6bwS7>W_'KS3ӤRTzuc%MH]eGdC7U!Y6Mz+nlK7m˴Q5iUc5QTj;Ucvpnq7r'r/7s7s?7tGtO*@T)ug;-v(v-MB6x7lÐywUNЊ7lzoU{r|Ϸ~?b7u >CN8'/87?8GO8W_xJ)gc42쇗w(p7LNI},͌߸pSW-۸C y#v_8,8wW9h@+[9w99y(9ǹ9׹y߹9繟::':7z;:G/W:Sg:cwz?Ds::- ::::y:{C>9$9d2#<!8"9ǹ2Bk;꺊#yA|:yCõgw;;%{;k{< /<9(|y¿c+=CsC7t<~yyٽ{c}>;~ճ ?yos/CCGA=@1D`A&TP}Ѣ`1hC!<ؐdI$Te˕)]Ɣ9$L7q|fXK`ziGC%Ui(6ƆS)}a\榤ci:~$'ABCU{/[INT(tlٱ(Qbu-R4 \ܓiAɹ.QM6jU%Ñdn(*A_7'(oմ[NMWJ_R@Š o) 29J C>P20@kp0X3 B$$ ?C =$E( _,SNLHEXIo:TC5<4PF!-G6鑠!KN?|I=))*nn*eB%!U!ku,#CDח"0 Yi5V\14ؚ*B1ۜViEH k-)%f/ ݌ 6!p̏r{}ޝi+%(5)2ZQ 6\a^SWJ㥭 dSa$|76ↀec2 6B*dޑ 22.9•[&gl7ҙb4~!R\d'=YHPM,X#Q%)JXQ,-1YK\Q|c/}`Fdd97D 1YLhQӜ5 YMl"rf7! N>~ShbbL(6ӕ夥;HNx>stg=9|s5o<|?ʼnPp* ݦC QӞEDot@|fQ֣HIjҐQ<)KUҖBK)iM2'4)7-MU TbN|H}*Әԥ+ERT4jUө8jJTԩ`-NJ՞W]kVVժLR Ք6gNնuX l\ռ.֯|i^JWFVr-ZW*E+\'KX^V,i+XZvE_ZК֮}mgCѶ6eoUkTdkqu ֶ!A;jPbTԭu(ij5w `LgAۉ4wť{m Y-z׋cpꗌ =M*8. a12anjx") Lz$j"&&bN4ic)8*> NJ,(<`!94>g!ޔrX^Mf1e6ќf5mvg9ϙug=}hAЅ6hE/w;qFOҕ1iMoӝtц$ 6QjUխvtšֵqk]k.@ka6͌&eٹhmiOV kznq6 Tvoy{1RMo}p7p/ w!qO1qoAr~@,n|-waX‰ qs/;ρt7?`9t >SJJ> <:+14ѓ58A;SBM9e3A9hZ@ %G6SAB6E;`BL;UB-!VC 42ӳ7)37˰3vl2NG{4#0G~іItJJJ4KtKKK4LL, Xnn#2@a8 9POz$tOqM8C!p)J#uNmor)( `bcbAa44O OY/)SO::0>'a'ͬ 3 ,ō榃Vhu) " Uuґ)QR) }u|p1))$ހءYR\}r5[5 ːµZ=\bBc-VX,R l#Uq8'et`md'*oDeˬy `i_; /Zu)pUol+1Cal5 s ܈ Bnk+R~NCkZr 6r-TCPqmAq$y  rX!#]u u:MTw7+{szRry7^2wd&''m+,(())u)wWɴ2z7{w{{{7|w|ɷ|1IѮ'Z)Iѷ7G|HI7Hܷ4}}^1CG!;/I뷁ŬEwEk(X5g3_RLBSIb?w83)̨TO4#JD?5kq X~XڞH3_؈͓'x3cs8WTxHi|+T㘇}XH5#[-J=yIMQ9NX]Ceyimq9ucy}9wyy9Ym999r}ٜyoYeٝy@yl!Zz9!ZYZp`٠!:%z-Z1:e`cAX o^ ah=(!9o7:٣"|zjYvqAf٥a`(`( !,22!RyH*\Pa6H1aD/jܸG+91$A9b$RĎIL7EΟw,ЊF*)P&IbJ=G~\se֯`:˲*ʓ^!,3,22@{hnRƱnŬxxZnoȹLfhafr[`^scyŃƯXUsX8p4B:hXB8ȀC(\81E  À D$ p˃XfB$q~/@Q& h`$TS:(4"+`s9TH@P{ 5J`{Z! ,d22222222222222222222222222121212121212121212121212121212121212222222ބcURRQPPOOOR\töij²綫ޮתԷ{bI@@@@@????????@A@@???@CCB>:88;>A<;}>{DxFr~Akv:is8fp8fo;fn>ijMnf[ubd~aj^l[mZnZnZo_rdwi|nvy|VFCA>;7455w4p4l2k.k%ky"iv'`z,X}/W|3Vz0Ux+Sv(Ru&Qr!Mh$Ig%Fe#Bd!?c$@d0>d; N<%ݼ_μУKNسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@},!tD=c>O35\Tau 1=yT2  m#=n|QnP#]n d6ՃIHpdJ%x'wR@ @#wH:wԐю@̑1q@wx]*i'*KRJn8 Cݠλ@ү>@<s98I'RI< prJpR}߇?w$y^a՟ԺA `Hqⷻ~_>O#(@ʁ= )ΧAnxB'vm#< 0 }8̡uxu.(L H?|#0DpaHrp"%"1n` q3 yAQ?Ői'ha?5L8:㎏TJ>Z$d6JVGJy"$##A& a\z5L "" $[A|I[R /F:1> Zp )Win;x/ E1$FSc6o!y@SEpT+J[8#@ڸǕCrQJ$TzW'AXͪVծ^]Xg%5(M)6dqVr}+] Qr~%#`E2\V2 U+a uy]`ٌD`5XEհ|`3YҕPi;XKgZ6;g#lfock:YcNʵ-o{ks3MA ЈNtFЎ4!-JҖtgLkv4GfQj65W=UԪ6O֠55]O!O(D'8!h{H3ٍ~6-i#@Ѐ`Ù4Y3A&ڭ&rhn7jFͬwû64!|h34P54@c.33fh@B wI &3S畳p2܁jŻE! 2Gd΄g3 9w34|3 DЋ:79\89P9 d!/EJp;=xI kNs0Ȱf42% 2Bj>z:͑)oy̏hŋ~h|Soz3gB8܌*" sI>ǟtDi,5|uzT<̪_65g's{_Ϳ8ogh;<}o([@fr 7ŗU`fЀ xXf(h 8؁$(X+8-ȁ0Ȃ48x3;(/h@x3h:؃H(iIXCxBHfL0DNMN([F?XȅfFfpdV}xS~ztGW؅U%!xdtm^C^tpH~~uexx}HX~VO(|h}rsf h gfއtg8Pwdq{|'Ggzff}㐈UoPuGfޠytv}f6gtX{LxgyXf qywwgXx8xxwuFgevU#9npHyfM 20jx_g@rwryr{ir&kǓ})urYjFh)g镍Ix)f )|YjIg|yfim9if mjs陫l W9kl VYyșiU%YIeי)͹p9Iy 鹞 ]q9Y9eyZyj LVgf0ʟ ɡ! :Y&J(ѹ,* 4O :<ڣ@ʜCEʠGJBɤ)+X3NKzۙ\Z W*NJLj: 4PpM JhzsZu:w yڣ{\ڥ*ʞ k4]r V_^m4^_9P4:z^ѩ*ZZzgꨠʪ:*ڪ:z*Gʬʊ::ZʫJF˪*⺭蕬:zX`j`ʭZ:٪j{ڰZ*ĊQ:Uꬃz-/1+3ۢ5k7};ˡ3*a`9+G=KkM;OQSI۟Eg L5ذWaUegik[m+o;IcuKwky۟{}\q c a[ ˲븍$F;‰K )|Kk+f{("U1M V77rC779u7|7g@C8J+8;;S:Cs<L@@"V#@4BB d R2aRMջˡ'H#tHB<dDDKtM Õ@ۿKL|47BgLVI I@I `Vf#LQR;ɄBm5a9ROqOO40O P 4P ]J+@3Q!U8u;>eb\f|hjll[" m"TdEu d T,bY]XV]?&!~ ȃcKfd%bS\QdG;ɠC4!]R6b2fYo %1ʤɴ>|U˷!u|`F9R*aP<JͬZx,c!| M3*,،"|]\c\<-d ,)C+pu5Ys7\SU 8 Dq-0=+ȫ5O[d7 =%X*C_:5& ]6tR<&8\=@D{s7*e Bs?w0<6bcRU w D+sABCN;<@L<0S\r4s=u}5DS#<:vaBӽӅ<p-:_c!Gu=+dBOCdDNӍkTO BNn N[jF4>N’V<4~ KAg?Hq>Js0L;?L~nGI@;QF.5r鄮h.≄CVF ?a3uꜞ >Jl5_nGGra#4ߐ6ؽaL6Ю~>Y N_nT>ߋ-^KѾP~DrNONP>WmE|IO:נ QOdSOE6LLnA=mSS5 =h]S[ :po5rOCRwmA]RuO o7|?F:oiMkЋSŞ?2nO]\6? zP5a 1R,>>#ЭAa?Կ0ׯٟۏ߯(N,%qx\dwz,eH6 q%O+`BDPD-^Ę@ F#Ȍ%MDRJ,7H[YїTАaGDZXcTJFTȆfj֧4kTZZjVۦm=KRnXv.TE L4΄%|׽i"͘c/ͬ+箆b}rY̟SJy4b_\=k}-sq.YqO[=k.]BN\f+[NuŻ;>D͝h<~Qj0xr0"A#r@lAd #|B-LA!PÂBIDQJ( Y IPEm?WEK\w{,0IȈG#gtRK1)SkoΫo?*p;-{δ29Ӳ0KܬmK%?// >url7䏽Ftc&UO)eo̱xxpGz~){ <2IWERQ>]}߇p<%hw.zǛwJD`ȯ] j_̀M"[0:T- & 'Da -!n!%m"q uN?bȧYpGDbD&6щOb8E*VъWbE.vы!WgDʐ%΀*ZJ4 P Fюw4-V~c8HBzLN18(A^$]/IV d$1I=ZDڳ(Z] =a\ ه1NJO_+a;YβuOd\"'CXHbcB }Ƅf4GU7$#Livӛw|,nDg:Mva:NwӞy+D^'[V( m2 f ܎Pjp#{ a[T浻n0ю)?v>IP+vWz~B3w{_p'^e28߶x yC !Dlc|ew)kh@!8:U;enΝ\"c~q<-gmF u]'5orGo+4Sns}CANGFIqSzp@컏#m~@o2 8<+xc^/ǃP)a/G~g=#oӗUoa_{Ͽ~×i8nFxŖěSD#NgEp'p8l0kE109d51L,BDž\$`qq.֡O.|E aޢtF>H_^:@޻ނI(a"ac0cHavH98X2piȃ;(;^фuF:j-?xj^VTl֠n_ RjHfl.A j9kkvO c;V8xǾg˾l߽9Ζ߉ߖ^D(c8;;PݓUnNzP/$0F}ZڶvF_Ng/ΡvpXv`ɇ jH·z6و?upo`7Kq.fgamaHo_$'CP);HFj0P8//'&o?Hjq^( ڠ "!g"/6b6'넠a&;Pku7)kk07:o`𾄈/ Gt-/Pj{_VsΡIu8(b!b(iPn 9btLqwqfigWohvm/ok?zvmqopgrGw?tg8^vw渕\)b*Nfx5" 7#1eHT6)A~wߨHa;Ea@Q^ עw(pjlQ Ux)_ǕbByIfɞ{xy1xy0b:y z_ `?zCe{}xVc$q rQ&NgꙛjE@|8#w oxw oz;97>w$}%zg0zHd6 wG_ݯ]G]'f26BOubfii>vvh<2(gѯ*݌.Z/>c?ߖ?gi&Vɇ?@,`1#HmN!x'A>iTUR2,i$ʔ*Wl%Ly04/&Μ:w'РBSsL;%gFϜ;'Cr+ء2AzdDE]X֑{S/ʖsIG>1*mUC78{opʙ\}<҂n$@ 4\eFXFQ6XeUvI !(TdUv"-(T>EMFeV&_x('}E"AXa;_E;^cA<ӡ$s Ie@S5fidɚ[V`qACy'rh8Qz]f'nUfLII$"Aהgf F[t3Jxט{ xI.j&A%ę6= IzpQEL&p!_skP)!Uy]AU@i/vE͸|qMA禛h/NJK.$ܻ KЖ!bROl\%|2^Lr-21<35|393=3A =4E}4I+4M;H 5U,5!VkmJxY5m6bgtSoq}Jr6j}tn'~uۀMtznx~Hm睷n 6S%i9-vx3~z盫౳n;nG~;>SuM~;_wҟy]O?Ww7}z;֞!| "@Fp4'XA "*AOZp!DCA zg )t 3\!sB!o IR3D8/}G7P̙WQҽ*ZERj9znbF̱ #_щo#>&njLl!8FM]!8$y]r\&5T4`(IQ/Y+c)YҲ%.s]򲗾%0iq,fe2|&4)Mf&sּ&6yj:$G:)qf<':ILs&;)y3;}S4]M4(BIPbzL(? щ"uD/Pk(@9Q3$K ag$ NHDEO RtmCkN=(OObA>h &2:Zc" G]&f L3S:U (fRTc6ʴ*U IHXG5!|h&40.4@RM81@5M1 1bY}+B ~B95%qjv lIid3pbB w81EVQmmZM><5b -jUk־vm Î#i5 aˬC#@Gj6 GWcZXqQ5^w~CujD[_; ]&2,W]@Ad _f9AA`~9SGM]-TB`bVڗNb6?CW V z nV BWK` 6 NBV_`.X `"Y:nE!KTH~!!Jv!ơa!!!! b b!&""6n#:$!#N%a%^&&vbz"(ra&$"):)"*KB00=xa+z"+b .!/""00#1.b22+J/j$>r=z:d6&;z",DRF<@ b&a>aFO2d%feWecZ&ZdaPfMVLdUR_v&`rlQ&kfK&kmdn&d.kfp¦m&ndr^&eoҤgfpl2%K#c=Z9zg987'7Hr''0de"=0B/lzc}v}vg~~!{6.c&〶KxTF/0|K0Bh{}R9Z(&b'jJh/zh(Brh- (h('(}Rn*)()h茒␖bKTiD&)i':&B&J%Ri"h6Z)3j2)(:0)))lC(؉֩ީ,4,)<, *&j =dJd.NjڤB/0Be0B<^Rjا"xI*+FN+V^+/i4(d+ceB+k>#++"ޫ$6<Dl,ii l]+6k0VBO>0&^~,ȆȎ,ɖɞ,ʦʮ,˶˾,̶,}C|"6܁DHF~4Ed A $@<̀,.D40=_I&E=|`]!|@A$ĺ|CDgTD؆ZhE[|DH ն, $SLAಅඊ(EVA*R[E@։,h-Fbl J́i8.bjn+-JnBJF0l>GGr,N'HƮZmf.z<GRѲ~po~ĀTnǒpH h`ɇ`XVfH>4ɓDPIȏpRFoȕH$Dv؉ ʚĠ/lT  #(AAIJLDւP˰˞pK,. j1jRI1dDO[X LT0-ALL ŸKI0ӂH  J$Loq+1DHʱ"/2#7#?2$G$O2%W%_>(&o"*:莈"Vfs"Jh2IrI)-"Ęo,/0*;t-+v s22=s*?+[573d. 06/+ 313,I3[3;32_s('6os5ө:c359>33_rBBAk483ACs 4BWs14Js/tA4M+O:?;4/O3U.r뷺tr)48/*34XAu.B5Xǵ\5]׵]5^^5__5`Da[,K"a7vs6cOkpH6e7lC.cvf f"'R6j_kb6kW6vC6m׶m6nn6oo6pp7qR>vq/wU2t *k7O7vqwuh |7}$xGxw|"qu̠wz7|Wy7}7| HStH"Xtvx:O v'7?8GO8W_xĂo8oxt8888x88x˸縎8️989/+9?ĉ;9W9Sgycy,py,x9yy9W98"xx,A|/x.ApA% \By8ā?zox(@-Xzc3CkxzBzz,$7zz®Lúo8::߹y9._BKB?z׹K"BB7kǁǂ,;~ow>sk>ԟ+<9B웾}ٛ}3y?c8܃xCK}-hx߿:A_p{#h%x{|>;;π(~˷z}??@<+-faC!F00 a'N?R4PD/aBk]5GH& ,x0aLG'ViSO":LWfMkW9J%O׮ƕQr ̥h8c '^!ڠ<ؖ- qܲVp3o3Хd;@E0Y(PiҜ=scC6X;^l;mF>xqݻH&W;FW̘c u8l,:k($rOⳏO8K.(PӀk 03Gb7U\0B=cOCKDw*0r)o;ZDKjaH@KXZcM=H#'D-ˈj#L59.5"=2t;*)@ńP: E4Ѫ3G T9KS4URW1 I*YU%ai)cAXr9d+/\h227 3\Ku60d'>h|D$ڈse2PXרE Lw]feށ\qu\lSWa~}n筷\Xc2x=x4{-1젉=w!\C^hW)pXsHh*X.lPc~,%rt0*b%2i z)0~Y:Zj>(뭹*PouB la#<֛jk/A|-?ժ:c腊\*R^jy.3Ŝ `gs=Ϧi}w/ׅTI%yL=z_ޭ_R%x}׿J9?}?1~ߪ]}+v!A&@1A n =B"a QBz+t ]Bΐ, qA搇7a,l XD%0KtD)V0Sb>xE)fQNbE0Qca?@YhFQ8cψG-Q0cc)Q)$d!;t, H/Jr$/YDKf &=PH>2+_ <ʒ%o9C[r%'EÑ`.L!*3T/] MSJs 5ECNKu z z`\TvU#xt>v#F: )N>Oo>c`dK;].1 M`D2#| {?#?0|p`b.7q'#A0.L!pöo.#o.oࠡ8nJnJ/P.&0A/8AB-.p>pPPX#!2oˊ˸:p q a B H".VP>A>jH# H*0 g % ! 00Н.8o[@ O...T N.@1k>b)b e@# `")0(qQOO1m0EN1#$.fWꖂ lQq)O.J B.~!iN& Q.p)1HARӔ B2%Ur%Y%]%a2&er&i&m&q2'ltRjMz%'LL)wh))m(2* r%Ð'(򔾒, r)NH-,r.3,Ւ. -+ *.*.+.R.g01~R R%S,10Ҳ00R.sJ.4,ԯ2S K6h,j6S6zN\$A)*3;s24ײ.Js1q39sa8151r93.sS03> Ք  RE-Rp)Pj@^ @uS^% 0J 9 M! # 9 pa paU(UJ\#kX5eLTtNU+TOUA cUgGQc &r`FIp"1rfUvjLrqU0̱OMoPQV/mgpu2bEbVoV#Qjljn?UDo#QI.2!+!r;D$+%1sA7tEwtItMtQ7uUwuAMɄ+ I@]vMs4kuuWĊ?)T t=cM, 1;S0t3tw3yxo533?:+- W>y.wz7y4?]7{3ľ|ͷ~GO:sW:ǒ{w}Xu.x L97}{W/ 7sJ73721xJ$f8es6؃ K1y3~׀|383B- :8:϶҆kw=wuW<:SY8|9 Cm@3xi}Es؅ݷX~wx͊,B8xٸnxx8xY8 yyY9%9!y-5y95ٓAy%YIؔQ ]9em9uq ` zyy]9C`f9?9y1ْ A렎 @5ad ywmYّy  8 yd@!y EzMڐQ:_Y`!֠dZ @XYDA#8yWڥW٩Iyiz@X嘨@aȣAZ7BGzz:9 Ѡ)$ڟ/ ې{3ᬱl8@AMڵZz@+37۱Y{mĠ Q!z|U;Xz .z@.{ỽX1";<ݛ;|| ;#;!|-<7/\A|U|X5ISWo> (A:-H#' !,22!RHp`*\xÇ# L`3jȱLj?l(#ɉVDX#×/[~ )`M7'9&˚  `ѣH LʴN]*U*ժNb5gO;VɎe+MZ0R|;Rݳtuލ_!,d222222222222222222222222222222111111111111111111111111111111'/)(((((((''''''''''''''''&&$ !%''''''(()+* , , , , + - . 0 157776665555423444457<&D'E'E'E'E'F'G'G'I'K'H'F'E(F(F(F)F4J6K3I 2H"1H%/D(.>)/9,12-21.110/810D/3K.5O-4O)6R%8W <\ Cb$Df%Hl)Rv*W{+[.a1h2q3j5f9]Bi<=e<g4=DO=XӠbя~D㊭T%+s ]򒏾e);2~WL.XacF7(AEmr]&:mV '1Y).f>IDlݨ/!RVbsCIIγm(FӨэ}A)Bά+| ~2eGV}Q˸&"%zJS~a [&pA|aCvWnQ$0Y>汎 0ޱs,dH1{,eSyVfldq"s Xsrw/WE(xZrM Y>SFP˼]ӦU3#gЇ2z]~,DO҅R83|X]Z͎r5mh,z7Wn}kЊͭkERNXi5̚n_N|hukJ`XSl0KhwVe:gߌ5prP%#KZU.}:I:0ln%D`)ضuU/=QNfmX3ס%iB]r*bw61mЊsΝ p2=q':ר!EGվQcto^QC{]d.u;#wD|E/|%oE><&5_yW$=z/=}Oa֫^䔇6Ͻm?O_O;A8[Ϳ{_w}_?}{>|iH L N~g~wҗ } x~ x|Xw|8} ce`~@؁ǂק.~1H}08W6X{8ǃ'{B!>G|;DŽ?O(ķPh9@4#XN 7XȄcexgiȃkTXoȁq{ZHxȉȍ|=>8hH؊帇HوhXs5Dhsx{mh9!#ɂ%'ـkHSNHhȓ*)3 "~P}BMOQ)~S9UiW;>[ 'Y|a}cلeYٕǖXyhypItɔty^uY|I~rك~g뇘q1 PyQ% #`>HL ^ @   0I0 %1 rA (ȚO @0@ @MJi "4$  ś @1 p p` I!_ jN y Pi T ۀ VDÒ* J & D4iN,z9I4pp :Dp Ls'$:Y/ZIJ`)*!D5G$p!A :O:p@0 ɦBI@]z$YNa: QZPP{0:Zz"I"#BCvsicCB& P=AC@⪬ #%%:*!Py )[!(4;ө <!ʠqe!8?#?}? =Bث9@14:D ~;zB !"EZC:í.";$[&{š!b/) g 0yBKN0  P@T %C{^`H{ jڴS iVI˵`+bKQhJ pK˶P+=kN@+f =qP!IJKx M /z `zM@ z+I Mk "&e;  ^ i _ʝ ɟW ;Y;)S)a[R V1gjV+[˛ ai { U!۾;+ۻI B [p q v, iA{;ɤkKUkKI!<#̟%\Ia m +ñ@.$S6TjHl(\O Q]  *Qn&Hm\3z53 ^ o0D,r5u|FL[|Ǯ,J`k},&ȏ,5;ǘLJ2:JyQvcj\; @M <iƩm 12J&_BZDsɑ,5)D|5جɱ DC L€ހ[NӒK+]~"4+ACEGILNPRa媁噡BKCk9\9z@tMmCs3!oNDu9;F|>@QkC%zʥdn8ΰW@"ԘAo9.vDy^^T2@Ɏ|A+KwKC>ͮ.TpNBcB}>nN^ k1yú0QnΎ~B~1ވX Z?Oύ +!&4O)" 1;n2q:~GUsFln.@:0dJw .8HR큞OOvɪQYe^> cOLaN.>7a+1ϱi-!?*Q~oNAD $zrA/SʿП¤)#:Ib.ݒL`̭jשiد +Aɦ&ӐȟA[ I!)2IB >QD-^ĘQ#i `F%MDRJ-]SL5mԨLN=}4VCƢ+¤z8 @8R ۤ$A~q!GEVZmݾW.ɟu=ibHD"UʷOZg*bE"1WiYfΝ=ZtFi 0x'QOZC8`)[feu\pō, Ӵ-m!G'ņ`'v\͟G^haY4TڡopHšfc@D0Ad0^`A 'B /0C 7C?1DG$DOD1EWdE_1FgFo/=3|1H!$"LGtÜ,%!B2K-3΂Ȓ|L\ixCM733L2SN871v,.A{*nPC)PAU=Ӥ3RI4N0L;QdF"nTGK5'FSSYɹ XgXg[yZׅYc]hJ6Zi˳TLM55KY37\q'Rls[|]f`h>paF_mDDoQ_ąQ[>s?n\cQ  A]u/be(=eXq1=+ؿ׉7Wp&[/^{қ&XKsY1e :Int{\&G= 1VH/dq1Bq.NX\b0G<c{F%:L$4^u D0VІ!XBk3"ر&֡‹_` 1$.Q4|"#1KgC&R"0B+q3/ţD=2AZ1d}@"7rw|8B$c-|!NN"X4\q1WD#pC01vŮ9ґNx1Cf,& )Y\d3L1bL{.3Mas&3g΋%S'd/h 21YR#8M|Ԙ69T1U˄s]1&KZ#p͘>N^6.F}uiDzG4rM E c5=Fƛ:s*UN{I}p*f1S}H]O}Z>5LP*uuª!Ԩ9͘KS՛m=iȊҫHXLEVReutE`Y!LV|<,6  !< ZCdl iϢV]mi#+Xb5$N16%iaגַum i1d%cnߺ۸6M.Ʈ"7 t[)UTn6ڱvծ{k\kmVWcW/Mݩ ̮7c`e|\ 5pd۹)O'-O X0Og.w/mԮ,ьy{Y+i2Ў4 X0fδ%7pшM SV34"TJ3rxWj\Zp9imecĚ5=K[쭵^hld+{)T;,t㰥]c!>vSkJc0]mIj^M54TLY8ܯR(DSb񈲆6\7 uߎw1[D0jY̬r6ס[!E.$uH9goo`c[3ڭW\'7y {^6O(.{:כ栛K51${urk o.䒟bs>md`SYJ~eE,d<6_Ћ<=S2~3&{^fr3~OY__> =m/}S?sޟ=&d??12)J??)k@$ $Dd@<@ @[? N@DAK|@\tA Aԛ#!ipihAB!@# B$"Td&'B)X,-BWLC2,C34TCc6,)|K(&K%K$K#!K lŵ\ L1L+\5lL|KLL%l8LL MM;,M<LM\K ,L|DݬM M Mż,ALD%NM|Nl@@׬$ 0x` ^X/؏mHIV02Wu py ZhIO 8i`5M= ԅX 0}pdB1T heȊ !}x҉Q(P # ˆ Ā X 3m x1j8PhЅ q-95C:mxP҅H{!w`BENԸa8Ԩ`xWXZ[\]^_`a%b5V[-tlcX1iyJ i@oTmy=t6nukVjmu׀q}hh@WvW yJw]~9roׄL|dI`a]{)U،=Q}ty @5Yb^׉.00Rxap/ab.V9mo!fU#e}V@0CD6.Hϒ^'tlWc1u2=S8z׉!#{ox#YF΍i XvbwWnYFO`_Qo yVtXKdܨWV?0ffe# q.pGWcNhFӇXay?٬gq&gsukr1llhOT=hiPaaR!u|5ѡ膸oǧ. q Gׇ٧ڷޏ G {5ӨY.r{jhx~pɠEt1k~.Qco vx7>'> *aV&L(8p` 2l!Ĉ'RhbEТ#Ȑ"G,i$ʔ%$x`— YTXS%Μ:wfF i Y΃S*֬M ְ̣bǒ-{&Q.>m9٭/ҭ 7.޼(ʕZ%_AMLsU=rÂx1clj7VdȕslpcGϢG.mƨ#\uf̥aӞܦkk}۲m]i?0j|o}j2:g"9զ/ov\nzͭ[{Y?\~gT *SxwU_|Ba8s1| .UA%V]Qq8T9x"1nb,fء#=? 9$Ey$I*$M:$QJ9%UZy%Yj%]z%a9&ey&i38do)? MO?#͜}~(3rJ3 IÌ Y7(hEC DС$Т.DM7dۡ>C<3|: iLMhWgB+,{li̾ j3ψP5VSMAfin.یifk-6&;4͌3ː\[6NovDl#{0 kU9C2lCPS5`1mA>LPd\P,:5P+/2vMzNA = }4҂*iA:ioL*kSPWw(NC;qh+7dLOouBf})?_{E4B#=8Js~׋/6/i4 Q5k<B6cSn6.i@&>[bkt+Y߼"\ ܘ.]g?'Ki-!H^X)\*S_Ka0WJL3+e0# iUrBb,UN ()%Np-r^"(1f<#ad n|#(9„v#hE A(A‘<$"hD2m\##)IHJ%3)HLjoH!=)Arr\c)@Rl%,Y-s)K]tE1hp.K_*t IJi.d5l"r$7I`( @d3l"ߨʆD;O}:g$ PTD)@vmh-8OzBE1QpnGYr prR~ LaQ4zFqQ^$KQ#& f`(D5h*Ԅ8R*S*N NVU5_=HYիf*ZݪVub\Z0k]J׼ k`]rXZ5]cؿ"b-+YRo-\?z6gb7ΪV%-gYVoF5,akZŭo){ p}-pK\&mu 67APT>KuUm{_ןiyԃ Ѱ}`|>S&kvvNXnE؇qn܁Mނ%ㅹlؚ5s Oؼ=֯Evc1|d$/MCd\NsI*˄2yde;f22ͺtӬ&u>噝,;YkI3gjσdE3ю~4#0x83MG4QG+iUjB LWӺ  ek)&/w-lH:Ö?9c3ޑB 0<־vyQiBP -q>7ӭu~7-yӻ7}G(H;~F X3F8+ SB-+q(rW)LAp{HBm`cr B+99sn,\0΃.F?:ғ3N:ԣ.ճj] EA Y%M0ы&4$Bm4pOF6/ C ;Kqe?C@ )k$!?-H"i]"EAn(PDBp GLROAV&cXgH r{Q,]Z>G~ H${/3|BI p}A&> MDt 3])DBxyܜ$D,̞BD ^_WD-`]fq`$ PZD\`C`a7_y8D\-4 \@P@B`  h M @pPa!!XfۆECC_8 :_E !Y"FQ E4,B) ^At0^A,]/:B9_B "!R!*΢IQ&&JB")"آ]C#`5]v 5C%,ĝ0^/B0].`B87b҉LJ`Ax7|:~ E2Li#7*^8$tX#@@$AA$B&B.$C6$@CFiۿ5cވ@DJ$åDI܅FiGZpǁ|ZGtlqDЀDL"d2dBLI:tMNSdM^yeObxLPSz\TXMZ:eSvnpeBxenFB%qen__\^ZWo]"%%SjeU~%_6\[LebRPP|VHdVdXBe6HzLZyPdET֤L%GeiFnYOoNfnr.'s6s>'tFtN'uVu^'vfHUB|v~)fA!x'!ey]©'{R\Z+gg|:|N}&U'x@wJ(6>(FN(V^(fn( \"h(yđ>ݬ(FڇA~(䨎 NQ9ai(͏.)>i &n(Jڕ>fv^᧗)))tƩi̩)橞)i*&."*>*>F*VjZ*fNv*r*jVD*Ꞻ*-j'j'Ъ* +j*.kZD?LNCDv-@L3C.t5i&pC68iʶ,0Bk%tCpC&,`ʠ+B0췆k^k'CClv*+77^++0̩"lv6lk'@lJ6>l*+ C/B.`J0p:,> .EhB?PRv5`m. m'r,i%k%TBnaj)-l윮---iv,/ڊm'mn"j©jB,ƩR%XƩ5,ȩ2:.-ۺm.,2/B C.F"EtB4x?`b?n'0-*nb%mVBm.^.n`)©o*2o'/"jmo&20˯jnpvz**pOE`B?V5:65m O,&/06X/p2l CAmV^*J60fgqq'D2q0  *+X, /j1+jBp'p !BV!q/2=o&ăBC&r)l'rB2++r+,òҭ% &B /+@,1ϲ222-Fqqq',#?r-3r2w2r37)510)17r8r9˳9Cs/@:5'63éA33>q=+As=3t&49/3שE{s>#4FsE4=D4DF4GSGHIDJ7.l/˩/7n)?E8W Gbo%S/f;2B.iN۩Uo2ϯǩTSu"4Y{RuZZ[gu&ĵ*n]w]i^o*V*b3v5U:kNJk'rCg,"%+6Ю6/h30,jӮ6\^b+go0v6Ҷmwnqow0wm6fӾ1oVGlimWr;6 ykzG*&!mdvq;#WDxm'Pk?l׶+Ķ 젌 =/(u_J避-(g&(w+. 2xP8]xYWqwx₸nBxf8gs8ļxxy7 54|LuÎ';7dsF/CsizS왷j_j*^)y yw;v{z+rq7p+zszzky_:zEz١uҪѫ˺z{YRE5(4Cx:z:)I%YD샅pXS(q;9{{yRܶ;+Yħ!AZA{>1{WDQͅjl-ftulEdݖçuG'v9wVgva<Ç_ɖt<\Y|ߥk|̯wṣ|?dz|k Wb'߼ķ|pOKO}W=[}3}ǖ;=;/=?}E{9;ܿ{ǻgD'W{{E$]ORXX`Y7X7kSWT>þ>>~yFѾ~O~?6?y;x[@> \3^@?x>G&sڧdk֢yvwhB?@8`A&TaC!F8bE1fԸcGA9dI'QTeK/aƔ9fM7qԹgO?:hQG&UiSOF:jUWfպkW_;lYgѦUm[oƕ;isBo_;MZҦ VqcǏ"5W`-Uj}+P :ҁhaڂ k v#c}$@ w7߀%\F9U^zJn)Xc5[MS7j 2WM ELcMgQ4H58m @< `Ԩjꍱ-ւ<8$-QfGᱠX։.hln WZpѴCh4 Xel7zȷ 54F I""=Щ-M4!`W\rilkۄڑmߵPaY%ȑS h 2b' kt{K/w EQHk^nOA}bg@4@. tʇxi;0A d5)@>XԀ05OXB kDjAV3, I4 p1 sBDLvxD̈ !AD "e\!=K+H'pZ #θ3~B$J/?`9#(Єl$#լhEr1acVՑNqFL"-&PДfs)dL/4CfHK_ԥ3GA"T=s+t=P5AP. uC!QܯZq^QV a(c@GmN- tHD$2IIď#{Vvĥ!Y/R@_L@iaSnE0ºNv\#n1e:S&INUH%=M/#T5D-QS5BJxK #,W k9}*ֱꨡ[cEcBd ba[ "cZB 6w|6?Lw֙FM&Ӥ.DL#+.me&h ɖqzv4U]8lfML`3Fg5{DP3we1L4.M rR-AVz^ֺ $&lΊenܚl:~(Q7 xd79YjҔ$+, a;jrrT9ύP\c?V26 h_Y,#ip1VIa&p^ _ħ8ž7 l64D) Ѝv!iIOҕ1iMȟ0 ͳw d07꛸c=$C~L3 Uӝlaja=ĥoNjDb[3 Y#=lmĖ%=]Fi`T]S;Sv#2F^݀94\T~Ae'Uƛ.x)CyIG*)\7Y7"e:?lb.{<ȣm ]rlUS+G*Xkuú0eF#;NG CRC:rue7ўԁmw{vϝuw={]7Nx/W|!K-yc^g<=įnzԯgzϾe'_=}{_H?>D܋/|G?ӧ~}g_{~_l/0hb{% J]0_ÞG}AI~afA~a* N6PV@DpMpRphAʯ^p/la!؎Pp. ZP`Ppppan|퀐 !"`< 70> 5 .^!:Zpjp؎Ap#OyP ( !DP1Qݮ!P0f! }avo-hqmFjݎ \! ]NQq Q q 1b11Qq 1 2 n 2r"")#r# ##ER!5"IR"+# $r$2$O#]"M%c U!q&m%[&W'iuV5yR&Y2R(?2%k*!r&w'"Œ+2+K2,r-+,r,2 rlx 1R.-nU؎pP222W3SPn`) `5OPM3!-p a 5?O888O3Ma ɏpn4S*Os5;:2S;7Q</8?S3T@^1TI6_P*p@'Cl!˳RI|Aۮ&, 5E_bfTFkN0T9GGAGw!6FqHHcITIIېI;oEiITI4BtKKL-HFTLJIѴ մMMɴVN4OtOO4PuP NP5QUQUQQ%uR4R)R/R1uSUS9SSAuTNTQRSuU UTYUVeO]uVYVm5Uq5WIuWyT}W%"܁P#.'A'A'A'B'D)I+K*M)M*M+M*L)K'J'I'H'F'F'E'E'E'E'E'E'E'E'E'E'E'E'E'E'E'E'E'E'E'F'F'F'F'F'F'F)F+H-J"0L'3L*4O,5P,5O,5K,2<,02-00..1./4/0922@76C<;@CG7FJ4FL3GM6GO>`<c#Ad'Ce(Ef*Hi+Jl,Nr,Qv,Sx,Ty,U{)Ty'Vu"Yq [n`fie!W,C/;07121212122222222222222222[5?<;9=888~9z@xCuEpyHmvIjsBfp9ak;`hCafKcbUh^]m\bs[i{^l\n[nZnZn\pdvo~yŗʘϷ۹ĻqYRRQPNNLJEC@==W_|y>sER+h\D-$^ƮK|= 03L̃.{ԱOr֟}%oϽK0Ԓ;ܥzxsDxʧD1+BGEl~`|Q 0VWD+#V=X ޒ;ۤ@y{! y6ßɰJ1T.q[/x1}!bt';EܘH1#"Q@"DLx-bqbaْeD `2;x輘C>͏n(H";*Z"A1.v1tC$FIY,$/)H#+:OVD-Do[T7YwN&=iOqԟ (8O1(DЃ TUh7/jMtS$#}H:MX/PpJtKY?(6qHI2LiE3P0MCcrb4EmS զ4Gs?E&u&NntfjUZ0T HѮ|īC0Nl@)'IcHC,{ERe:avDJ%򒖌yceYval[(RLpqb))1nxYTcb1ǂ̷t>\5m Y"& j3\\ ,+]D7ë́]ʠ=rϽك GO/b((1(Kn <U$vމbVstQ˵P¬ET^OXbNqvb8pL\!RC2&OIvrG*[W򓵼e.kU2if"s窹cC@:۹Ί>πAЈN F;уf4'MIKҘt5NҞ5A-RSԦNu hTՆv5ggYz5w-]־-Wة>65 q 8:+{١6;m3ۿ6+-q[ܸ.7u "輅fL@=l~úuU uKᘆwAg\y7ĹqwtCkGFUh/~2qyuqC…pu @ԻQ @}Tzԧ^u;C U@^g $k]fWվv_{~pN^w{xg</{y[><5_xsGw~E/{^ߺюusO}֯'?^'/ G=|__ѯw>ۣ}߾L\q,nhF7pp o o nxtksWuVm Z hnhn!8n#n%m'm)m+klt gh-l9Xl;(l=n7rAChEipGnKȄMjOQiSHUpWg?oIhYxg[_kcehganghkXrYoRilmrXsHkyomJx}8(X&@@ `mh# L  U 1  {0Esoh#PX L pxxK[lH#L  ?x{0{KH:QQ 8Q!@x%Dgh 8:@gu捤!Hg( tP Ҷt#(Y7I0(ɌH'P0 Ĩ( ѓѨt[IQ m(mQ. O @ 9"iE藂9Yyg %Vihy' I"ȘyY0Y 0" I%ɚ9␖aYKQtșGiIwy[  ɜw⩏Y}WVq)ɛYhԈK")o90 j9-":$Z&z(z+-/1z3z&I L;3 M0ȏ8  ؊P iQ wCj0F HzY٤hP HVG^z`*gAfJUz VzJN gѣBlꎜcEYRHtj\ѥ i Oi sI {Ф0| ;ULP K NyZ(xxP8:Jʌ@kHƊx!VYAɸϊ*JJ`ɬ*ê(xѬ Ө  ǛRi:q1:SIZ K"$H - yV:A!kК :7[&HڴفѰiC類!Zggk wEQyt6kgnۚ\i٭`Jhpz| o˲+@vvV!*ˤs[gQ!ZqK/{+B RJۭ9_c{am ֚&z~+t6S[g%yիwv[@gl ˟kfо˸{ӋkgkʋK:Lgؠ`Q+ ,Lg{gd:: u{Qr*E{<+Ǩ8[P[57:iQA<QĭHJ9|::Ҍ|mi\l L q c8=НX3\ ,|@<:]Zt)hLΤܱ 3 ," } 5ZCME=G-IKNPR=T!jW]YM[=]-_-&c™aJJٙjmkBAprm<z{th {׶I-|xmhxmf}m$iY :nim=ݡ]ퟋ-ڇ ׶+ ١ ܹM\1 ymW͠ڪНw!٥ۦ-lۧ jsMݻm1ߝ ߅܊`ߟmڍ =߅M-=ںݰ= kֽjo0ٞ9g,N+^0n}5-.64{qU>@>F>}D1:YBΞJM]ɽܙ@~~桽 hf^hNj-1o^~noNubt!ܨii!>>սNmv}z.v^ߢܓޥ{^ckq͟n<ᅛ;Asr.qѮȮ~^>t!{L }*m*=0몯Z ҈}P9UMX ?񑁞Y  ِY̼-@Mb?_͹ߜn޵ K8ޜ^SߓNl!@)I)~ם"`oiˉȩE~GNS. Qn`٦^TO^{/)?^4oeD@ DPB *rQD BQF9~ cH%*BRʆ#̐.”ISF;}YOZTCI>-xԄR^Uŕ]U+831YiWF9">-r߼x wo+ói׶}w[Q>B@:ڵwd0S ~F0JǑXrUDTArv Yiʖ1kn}"lٴR ݓSf Fϟ&Mv樁Ի|ٻԭ_WݴsǓ7 kal"{U^)ͼ"P,`UxX^an =PC}A_P -\^+BA:ND1NŜFwGKy/,2 JS ^:$(-uKL(!Q{G@ ; ΃&&dH2M-:dhd T  ҇.G&TT *V_e/҄r^m# TptcgMves>3BYu(mVVTVWOP܅U oyV_^V6sӕ:4Tec3!zv5aRӕ݋u7 %n bS;YvTַSЙday] ڹ~!yC1ߙu<6jɚnMU "xΔ<&(a X6T,ykTSj|r*_r3 R xڵ\!'(5w}sW^T0I/tP\u5o=;=c:w?߿Nȫ{i8't&;䎹뿬W~ G_ǵ:r?#\ɱW^CDLW>o{Y"ظLpxAbd0KP-X0!ԠN)A{dgdǒ1dF uY%"R%rA#hl\4gE!,MDSdžhMOmYƶ!$?g4$!dQB0TRHE@E(㏊ω[t&5NF !VjH٭cه9Gf+,"\@rE0Z2#6Egmf1(_ 2RNZyr%IIhF,HM=Rp22 #(.Q, Z# r4݄Du.?< <^DWu>N)T-)]iKEa.3%LHR:8E܈ Sͨ@P)J2Φ?9]*O&ӐM JJ ^V 628G~ N-UXy Ƞ-YSZva3RX)"5cY"&IeY0 hE"Z#5-BPvjMkPܻmGrkn-lᲶ-`N}.g麦¹.vK[t׻";^׼Eozջ򊗽o| _׾/{_o\/m%_7{/awx?b `%Qa-vavߍ0q83dC6rd39Kv2~e ?ߕwLenӾbPcyk2},wǞn2kwz/m!ftW|G Ȍo'/w?ӞͶ7/3{1㾽=E,|{X?o/'s-}ƧYw0}[Gտ~?$4DTdt@c* L p  $4,hV]X  <y؃x~ ~8ee D$T%d&t'|$V` T*ԅ*P@]•T8<025$#d89:;,)DVTX* ~T=P=CD&H ]@B CDD|@ z~MDIB\^_OFO\C].Sd@FCiƒ3` `H[pq$r@)\fT @/T }PDD WG4|e7{ȀCǕCT@ŕ`ŕ4T~hD:0CqP~ȍȎ^OH>R>T RT L,M$BePLŽ9W[BlZS 6I$N߄M3MD!\MeP$K4M$MLMMߜTo4OTO˿P5EUeu M %Q EQ e`O-pSX[(Q0؂O0 ]GEzЇ KQ v DҔAQ')@fWK|XXG[By]VREhRyh}(ӽ|eה E_QQRTQC@Uć;PD"ʂ\HB/@C5T2)RE)H K)`RR[@ [PI@CJ]D@ (_W?}CTyG8VDcA\ԔGamCMbhhbV^e}$TmE`-֔׌ [x]W^5yVX\DVcJQLծP+Jx}E VyU id@)[@[E|Az_WxV<<ěsMW Q-SF؃W4   ǔFmdj iFhj+@+65/Y hg$͕mYӇBZYURPW u M xDČיʂ|$mU VE۹MUjE+hVdT8튾؀=F39_S]m۔EDH|\J!ma\]&bM^Z>lQ-Th%=݇TC|ȗR4[,WfP"6YR&'I'N+bbc)L9c aqp P2"6/U4a5b6J7NDc-^c(7+fd1PA&cTY@DVdtN}cΔHeUkN[l|seV d d3.L>4eaPN/pwOKa)V&PFDσЄvφ艦h茆afŸi &^ʬ ;-/mX12A] =^ԅMHM(zV NNU%%3M Yv 1_4]@a PR,R=|A}B ]Ô@qj*b*Lv-‰]k 0CV ]AkEV0\FtUEZw[Ve\EwTyΝ6_N7'z|9ww>OҕyL޾tSwvg> ͎x_JnU_gz9F{e?-v/{L'xgx^`ɸbf~\{oȏz7y'_CʮH}'Y/i㚭W}ǭϮ.ه}}}} ~j-F1,}"Hʡ_Ῥ@PPP)h#ݧ}O}0@1*)~o"xٯ%MP BEj}X$!BCx^x r#ȏF,iRѱdž<젢2gҬi&Ę8wUF!dRVuS='֙:rsװb-kSQba m- `TOŪ*־]7aҥMJo}}\uHQk:ǻPH=ɓilM2%B-a]mܸWێs>=u9YңO1mZ(0pPu/~^!tiS>D YazQi ]Z䥑dyOIy)FJ*"Zlǎ*\l"[ڬRk:[ib-Z잋|9|;^T[oѶ'/xrw!Tј?.ꅰ&* ۚP>l %̜@CۖY1ď!݌=h^3\CU^aQAA= n9A5[bRyvͽGUhUlRK\&mwgS)GPYrLczd3iW ]wD>g~uIQ aW.;fcvRG U*\9@o3T2'5'oN+'C$"HR=ˏ5~t:B8*B!rO; AՐ@HAן@;-" qcY"!jd仑P|\% qSk)LGXLjB':iC 9EoDiK<mDDqU =E&NQ|] ;E1e ×FqymLǡ5v#=~# )A<$"E2|$$#)IR$&3Mr41BIG<%*Ȍ3YҲ%.sK?#%+E`1)L$$Df*LgBˬ-WIeHF2v)q<':yiJElf0)wst;i}s휦IIyH |(D#*щRf> Oz򚦔>ёsu@jZ"TD1!o*S6iEsӝ򴧴g3 R}ThMjԔM(GjYx3Id^Wհ#>=+ZӪֵqa+Y֒Ud-(U5t+_W구5H{lS e+d#+R6Rf?ԡvM%@Om(TJY ,lc+ٞFSwl= rVl*_(C-t+*PKIt *T;ޚ..zӫwij+ҷ/~/,>03~0#, SxA8 0CaDABQ谈SӸ61l31\Y;CBaN~rO,M@Y}8^[-s^f^+Of>3|(RQ+9ӹv3=~3-AІ>4E3ю~4#-dX3MRNE38-Q/[U{!gWz5s[ךr 1^F7ݍjH^vlf~mG-q7ZَLu~7-yӻ7}zYAXͪ d{$@Za*d.H]4<> !=DSR`)@(\ޚ$4'9/hIn=ȣ.127\BKAqE0!"ta"&P9Ɋ2S=G.]`Yc$ZKrw}S?p((y$.I[ӊ ɼ$]? ?:0}$.B]_P,yk^WD77I]?{$^uI0ǂ %y^w#_?8}ktA/bP>I/~E<}.Ǐ B?@qXq>\P;쁺Q ]H(LLw_I` >pX Zz` I> .،QIX`ĝ ICI-5v9I,S2 IZIXH4ԍ顂*PBD՝ ! ~yI!!a>.qX0d34HBCֵ.@ґďDbX'aX D("("Q&Xp}",H`/*#2&2.#363>#4F4N#5Vc95f6U3PѠ86ncZʤ#:9;F9#9v8 I= :A&@B"$<AfDcE>E꒹i0`C#CbE~d<$ڕ䂠dC$KN䇹L$D8m$Il$JCB$L$MLddkQ -H$I8۴E۳1T&Ea%UN%PUB[XZVB[IeWr%[eV[Yj$TDG$HʤG~!HfQ*d&=eF.ե",?dB#F"f__%a&efeXdd$g-ucaZ^.dAJ2e>#k#izkNlaBTmo&pp'qq'r&r.'s6'I:'u]2dfungudY&wtx5zBy^zzzy}u 't8c'(&.(6>(FN(VtZ~Z(f~Z"\'a'lŧ։hԨ^Σ4(h(: )zeU[F㇪(2{VZډi疆))鶥)")֩)橞))j)*&j66*>NR^*F*nj*~z*О**j.jj*j⪮j+⪱*&63 "C3<.H/)ЂDCv<^ f++)ƃXP;CkΩ/&-īnk~kƏ뱺(`)$",r'xkkJ)-i*l2,:,F,,Jz|*z+4)x6׺).hlm⬵^l(pAmٞmҩ )(*ޭ>.ڪNV*vmJnRXnݞ."꟮n(n**mn./6Q զ22tmx-쪞z.Cٮ-v*o).ƫ)\)/*wo@ *0ǯ..v *n&/f;6Q0,CV.w|/Ʃ kݮ-Ip+Jp:1(.0{q?zN0k,q$@𝎰 j!7kTn1 3k-R 0(BfL%p%;]ԦA`CI`Cr+2I2--r-.rɪr' IB%Kp.q*"r,-?3/4C.k/.1(q)3_s5g󛚳:;367q82i99s::[>k󮢂ښ/.8'33:ϲ?G4@G3&HD?t3OF;/{45w-HsH3G3DtI.>9cJ2Mt@4NK4"3~w3n)=m*G4SSL;rXf/o)-iUâ&0*pu52$//iW53[)o޵5YwA_p5]]{ua^#vrZec6Ц0.B^*s(uȱn=7XYvkZ3m/p>)>$$u0'BvnvmpAqW6<)3:otqO-kg׾vwl+|wpOwuf?*!77~OlD0>[{G芶:3Lo4녠>ZcwH_ܞr=AS@( kxmxeW,)8&81'#ql9n﷢귕n]7D(v0d82K)[7ꖣkyjwjw0pꡗpicv[r3zczg꧃zyy_:zúz 3:3`>^:;_;'ڲ7?CO{&_;co;f[;{;{6;{ǻ;(׻;;;<ᓄ3~>?)?sDk>~5(;~+~?ds @C[~ss藾>Oe>{>C>~G>W~~?~h>Y>GO??{C>WK_?3?@T ":ѨUfzUkWV;X_˞U5ZVk0l N<ݪӶ}FWb9lܸ 3f)GhbMFpjZ]f+plP-4jޏ*qÍNrλ]9tQc}5޿ZCBo9#8C08p*UO ) 1P 9A QI,QLQYlaQiqQy R!̂!R%l'R)e~`e/ S1,3st.4l7S9Q(y=?pA -C 4N<'PG!TR1I &=1OA UQ3|@`UYmWa3 qR5[qU]y_ Va-cMVemgVikVmo LNMWu sd Bfv{}_hFd a&Pfޅ!8Z&h$B?AV,mRFQNYefHpXmŖ*,{@OFw!{y'x``tcRVXtQݠPj8 G 8@Ax@㟏~cBRdI[ r_wE 2-(mdZ)TmR&~:`&P ,` (o D9p7G 3aߔA(Џ5*[\)BPL6c&(Eo!<<9 ~f'J02A)q3%"ʦy!)dGG(]\ "t }8fC@C(x"|rG6,>A[B1LcFH@{ c+ND/T Ēl7z lAA=@n&$wX dHR(Bf]+(B=EA} 8AnC_37o?=4vAQt&T,^IPr+H9mB֩RΔ5MqS=OT6))CE֬0FզJz=C<1Ur̩5TU(E2L)jիkukO<ւ!RvFWܵpkY4BUVطn,s+ Xlґc3% Y%֩>֫elcUF1kt קʓcL)Z,c$H0\6q/3.YsKV=ibƶKEU/ ehE[Кw+]XLi[ VroJ!R+U#Vmu+b|` (@Ab%6Qb-veR ` G3UB (6]m= c%ik]Zfăm4nKYyỸ#a,u&3HJrN]3;Hq}>S׺׹yokAʧ/j75zg ^q!tmB/$T'LgUi3ѤҿPt4 !*”4[yQmj p cZ~D,@TVLxn6Az }ht[A\UE.t v'H@z~,"-6Vt_tȫ Uk:Pp pAǐ{8ѣ#5f\5#ԶF]ۡ2s)YA24y@_WSaI_(~y8Cѽb,zӃL[eWR3ٺa&A2Ϸw/&<]>6*P؝] %жSPZWH *𑚝DgOWJ(!@(to`E#:Q祌R!q{|u8eÌ"v u&F:^oU [xT0U~*ݯXbŞwNKتL˼뼰j>2H )оo'0, !#b+$빚]07NRз.f5$ WTT Ưϯ^P$, p3$ORл4p0 O/$p= Ae.4ί,´ ,D0z1q u11a!1%q)-5q971=1Eq/IQM1YKa1%qeam1QuKqB*}q5Q'qQqxaq1qoqBa!Q1ݱ1cqY1@aPr1 @ !!fn"R"11q^"jrP!!2!!)&/r#Q#}22(;q(qr Ia VDK"@"(,)1W*kra+őBt'w&R-g1mq1!32q2) !#-2{51*rG4OsA0qB % !|-8 5q5/9)9#:y.:2kC6I47R:<Ւ:6!66S<1<)D 'B19ta! AÒ% tA!AQB "BB4CC@-C7AC4A C=tBODQBEKtD'4FAFmCEqBoTEstF{TCi"cH[tEtG_TG}GTHTDkIEtH4ItITCJ4E"#;8P3K\tR-K)5RUB|!1[S9UVS=RWWs)rMTUA03 5UWwZo53;Z1Ym3AS&0u2n2'}(5('ӵ<#R j45 &EUNYU`.#=>UY57'ֱV(;c?(CVdG))na^ue%T,RN˲dMVg2orD!.Se^V[k4apv s-y6j6k%sd"6gjmӶזmv9n@VivnV&no3p 1ppwqWo#wl-( 6.62W665:5>7B$@F7J:&?N)B4Z4^74b2fW4V)j1r1&DP#36o:u,xryW(v11&dK4c A( 7z|!L|Ǘ|||0717uK j }}۷67)7}7y)OnƶAX}%X8wB\6pkX P%H%d!R8&J!VM!@emWBsg8u8"rkKbx]%|"xYxX%x8g4X8،x8o$xCx8؋8瘏x9aُ9ؐؑkbHؒ1;%a|+X20xO8R~Wy-^-W3B'tA J9zc,v4Z|{y,y# ɼ&l:ʈ,sfGN{!z%VG`{hvj" m&|.֪ Jn-xT\ ֞ͅče F稑nN2.(.,Z\m!zN8iֆ.¥azюnxn.]J`/4o!&>ov$ϣ';%EMQ;U{Y] ?f h ѩP]!,22!RWH*TXÇJʼnZ0c C (ɒOD9pˑ)_$)dÚ6[,IO'TPE{-!,22!RWH@*TXÇJʼnZ0cF C (ɒOD9pˑ)_$)dÚ6[,IO'TPE{-!,22(@@^Ŭ#7FM0-2-is4KKf=+8أ;9"6 ).>[d3<23N&*7^g3#&,s~59>.$HA*\ȰÇ#JHŋ3jȱǏ CɓI\ɲ˗0cʜI3xe *IѣH*]*Q Sdy2ʵׯ`P'A j ˶۷pH@Yզ˷߿;έ[TXH8@ǐ#K~;V݂zN̹gN j6ϨS^]R HPE@cָs|mVȉ!NسkνOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlpGq8'ux'y'}v'j8(^x(RxRNiM:aqFinix>*Cj꫗Eު뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,)0,38 s;Kd9 X@> ~̐>nd^^@1lOp H`A\0c `pA 5<!,~Z22(@@^#K_dXJ623NO#qxTHHI\CNP[ "~>xSC*/ #'&QSZ>6=ouUY_F^eEGA$Q HA*\ȰÇ#JHVȱǏ 3DɓG˗ WIfJ"m0  t(PBY\gNPk:˩j=֯KvXeB<[P[uIp]mꅛw߈c nua?!( T}*ʖ)c~{yܬYc_jF=.J_˞M۸sͻ Nȓ+_μУKNسkνOӫ_: T(>{p9 e=w^ @&@z0{0+x^yE@ {8 }*&. ``B 4Ћt#x^*(y:@eThe~ y/0:;&*$=H@*\ȰÇ J,ŋ3 ȱB=ȓKD BS愖8#$N=Q<PEj`bR +hJiǨVpMkرA˚ڵm[)Y߯-LWz [4Ճ>0kc>pb9zݜ BG& @h@H=#dx @ȑTɍc@ #4A/`Ѐ %N@((@p^{) W \%z pBB( 9Xaq +8zh #DpuhݒEyRVIeQГZ:e ]7% ]8&)fCj"!,~Z:22(@@^#6KKf23N"#~&$HA*\ȰÇ#>K2Ҭ)&NSOB4z`R SR(`8@ ԡ ٳ! H]ok*6 ><(^_2 D*FaI:pE0 &X !,|2xx22{{ xx_)6rr++!%[3ccM4QQ &ŋ<<5XXREE'vvhhJgW9ᒒ6BBiiFF[TTqqMttEii VVStt^^8//!yeeJJYggVVww OO55 >MMMMxx*=>rg '( %D@ŏ,XI .82H=<4` 3Y @N ~@ѣH=iS ::JTBX)S!zMG$H b@q5pY 8T &\A ^ -,LU</8pʹ :y2#a`ݚ5q!D&d(^ؐ!ƀ!,((((((''''&''''*+ , /3566655555530/03567;!?'E'E'E'E'E'E(E(E(E(F(F")F$+F',G'/D-3B24=;=8AD4?A49=016/,1.*//&+3 ':&?'C'G'J'M'M'M)M)N*N(N(N)N,O%2P+4P-5Q-5P-5P-5P.6P38V;;c<c<c.@c'@c$Ac"@c(Cd8FgNHmWIoWJlTMbSLYJQR9Uc.Ym(Yv(]z/_5h5m6s47:B{Rsoh_p^o^q^q]o~`imdVdiEel;ir7mw9s=x?A=:::;==>>>???@u;T722222222222222222222;???????@@@@@@AKXk}iTJJKMNOPRTZr֫殦M H*\Ȱ`#J8ŋ3*űǏ AjIɓ(S\ɲ˗0cʜI͛8sܹϟy IhGH*]ʴӧPJJ*N1VVSO7d ˶۷pʝK]qm#LCVduxb#KL˘3˜X?vzpfq6cխ!kM۸sVs?DNq6jտs\yУKNu?o3v?Nx粯_Ͼ'v |5~5'h;MG0 wG 6> v @{= `M7!a4h8NԎafԏ(DiM<&Px@6yPF))$MH\v%VeE}ihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L74D-TWmTU\w`dShlvpCtGxLw-|߈Wwm:%.a5ng^/~.{ޢ鈛~۪Ǯ۲^~~{غu'otV̢E-b` 1f,#F2pH7αx<G2^{ AR<"E:2|"#)ICR$&M#(G)JSL*WV4%,gIZڒ.wKX沗 &0)bӗL2WIe:3|4iZӗqe5Ynd7IS,9Iu|'7)OkҳH!FYBcg=)eD(;b2Ԅh9*Q^Rش@G i7/*R\$=+SV̄3_*Su^ģ(iZSRj>:T:ԨGEjD:LRiP˟ ժ@jOZSԫ0kKZQR4%!X[W\+AׁU}_W䕰~kak6`c+YUlbX6ֲh=;Bv}eSR,lQZժuUmhsݲִ,m 򖸺o?VEntQ޵slvckݶj͕.p};~nw޿Vv.wK]mk^Bn}_j{7~{Fգd=iKavhJU'hCpZ~4OqelR{7JucH:ҡPbC.򋁬KrCP*#Ų@NPMsŌJ.eF9S6^f3B Y>9g6<@OK3 E;q]hJ+zҘ4N{J:xħ9 e XՒ@> : $x`30( do(@ vUJ Dmi'B%A키צHCBpa܁2,Rf;;VND"W"צx}<A G )MnpJݢ.$`w]VbA"Ⴡl&\(" (uR#H>G2w"Fqޯ9A PFOC(pÝ>UA @uAm<;y 'cK0eDޗC>#&'"H)nⴐ; `'mA7@Mw{GOۥRu$PK*/ zT >m 1P"Q{~6~'i~~/]̏>nz[G|p团htڋ2|JowJ '~}V|G}}hJf g~)ӧ|闀nx~BdC&u8~x~W'Hs|,0284X6!J#`* m qkVkqǐLcqvadg P9r_A8 !@  gX(@ y qp Qg~< k@mv&k|@4num7vnnF9Ѕ90ц&~hga| pP=x*֋!v`vi0Ks (njA? ?@jAsrো|XWKWvXrGlXH(׊(jh vf Gh ?`x[ !`&j 0ltnGdgWqtka PG(tAtMWGJ'l@)_W7^s otkf(io*{EHǒFtCTPv%"9JDjF)J  IiJKty@rp$5)>p ^py4ngvuwziIi %s9Y$hwXɗ藓uwH!p[%)yq & fyN8~'J*@GyiJxөkInYZȅ g1mߩw7xٟ.(  / xa1w~G}*Q0pCkƄyKQ)OǢ/R1'X1ʢ-d5:(r2ׇ7Tnh|&J d׷pN> IT1KOI<~Ȥ)~ja' (xҦPJQGZ *Lc|R*JF[n~+|tCz{zejz<ڤ#xkz'(V7dp2(cjDJRqxBڬ 裞Z};ګ'ڤŠ~pʀʦu:/vpB Eg%_ד9YZRfug1S'AVAj [ ۓ`Zw{]y+ p#МҚڭ:Zk p:zlQ`z)ڭ*{3Tʩ>['*ڳBk2 G|OۧMj @p WʵTV2;ZkK;En(q+KQ*ꬻzN+ *~Gf ' * jFN3iƫ{kTNk_4{wIKk&2k(Ë{[~ۼyUG-fDkh0k )hlkw$'qTJH+.j:ۊ;)-dmi;oh q8uWPW&x]'ZmtYj0fҦPp ޶&m)s5P *D,ryxu"oxæQ<oUu;.0Sk܍XP 7s鎌8j,.d ~q(Ywb 6ɯ(|lanizٕǧ4eL 29K%SEy:gٔႯq>sHttn+ɞs#i d LJjɖw{0P1ytR_L-dl&|!q +s񸍺 w Mlrb}јL.0~HIq nG#.I"^kt(G+H*b0G3>H2~[:NH=G<6.uHp@`dH]N~D1IpDQ[GXd͖` q E7` Dw ʀJ0]Dt^RlE i7p䌠DgMmD-Dqv 9D~Dl1} l}0GdjE6jh 6DDPp ̠ wSDG4nzFwX4 8 eꚠIDxDDOP w®,cEpGNpϮD(Eq 1`Dxp1t10 ˞DGD1P vPP AH^%厮 YdD}G}@2/9;H0`oD/؞1p+N10VMNoG^~V$YM?HH4׼~[DnEXDZ_ ]N=Ev_dqH.Goj^GHP }ٞV@4oJDp1p s_sI$@ oR0?οH_`ʟ?W/̯ԿD_ѿoyg.pa n #F5ȇBfx>DSܤDXQ)Z8ďITGn\I9Ԉ&ē9{dƑD2bM/c:MQ7 jT֩IYjڕ'XYKb{6,Y*^C&yh ^Xs9,@EV*kۮqC˷_ x'peIdqdolGZ?,,a(^6uьōOđn]tխ_nrvݽ2$>մvL~K/&=3> p?*H  "O 1f49)@G1 䐼:<ԸjQҿ,K$Tҽ&b>䒺 A/s29bIc3tSIH銌K4RMОL<T%;^aL7RQCR݋C3Xhćj訴K\srW^$2:-QD bZ}HcVe.f<֜YA)8p6頕鞝ꜥꒇzkS;l&lF[fmn{m离;o{op'op[qr)<1׼s9<@sKt+W}[wpM)k^I ={}p҇7~oyϗwnG{ڷC 8ީO|˧!'g<} q.G{Xm~v2pTH`Aظ rvBHƯ;!R= r.25PC&@ oC\ӡyC R##89PD%&"H4y%PܡíiBd8.jmbH)J=%v[c3}l#׈@YhGDюj#84N24LdH"6d$HBV2nL UNΑ$$ KRR*9JO.rD/-9S,!aiJe 3~e-y?Қf/8KfBsĤ&r:|<'Oѓ꼜; ħ鼁2;J{hf;]h H.Nീ-Bo+R*ѓmvkbV6Ksӝ¯c[)$ӟ-+ڒjԥ~];O VթO}jWYխԪ#XїV~UkVߚ8qծwk^Wկl`;XְElbX6ֱld%;YVֲlf5YvֳmhEEmjUZֵֶmle;v5mnu[ַnpBnr\6׹υntO PWnv]jƻ^;^¡Xyջ^w/x;wo~_׿+vW~Fp`EG J7p5awqE/]q@\#5sɮHB3!Ս#L^pywbODi}7MAycǦԻ"yw&:}FdDg|Fh>LDD|qXQo9Sdߟy1Y"ų!l Ly1_ky:`3#@ЛpZC{y@REZ1KLO$?Q,9[4cFv+UTeFtDfh6gikT5jm:nplor4G#GSxJp2g3`;GzGg_1{ܳ}T4it4]0o[~ Hhz4Xx$6bGśHkHxH,4)d6zHȊdɇa ITI{G|28bHIYI{H2CXI44Jhȗl JIn5E5JXJKPkPS8*$&**,ܶɚTʿ,Ka#̤3̋,L\ ʯ9ɫ L̜ItL*?ĶJtètD%sGǧ6"HM~H_MGM6\D0TtblNTJG@lə\.Lɉp,LtOBq|JDPޒO׬5H JPފ OsriKNK˹$ - P ]cC5]O%^XHomä4#à NDeQ e&('*Һ+-,/R/1R1L%K4E-ԅ:Ty$5]16 H|d)SS~NCEbNHӨP$1IʹDsI̥QL}T2G}Ȟ MMMJUSnPO]kUVWM,,7 THOU2 -2f.mem,igV-ͳEOݬCYU4d]ݴxm܊ }W,{VX괶ܤOS΂W ˉKl?)QJ_;<Kk[6E֏^mU$֌5MXTZYٟP=T֗,7 4SZNURBM̌ڡGڞU#TJڂ\WU652Ӵe\۶[|۸[m.ۼۼ5ۿ%5\Ue]uȕ=\ȥ˵έ\]5\U֭\j,\uU\٥\L0K]]%^=^e^m[ٕ ]Ss .0͸77XF>He能]d(۾ _j!5HH\u߼eXdX[߼}LX[E_[~_>`.ʄ `  Nm f}؀\`>`~-b%#I0HXȄ-`e^gd^@[H.X\MXք6;;݂bbPec}c_cc d%5_\dc:M.XndGLd;7cOP5MM>MUVcPvGeM`a&fMZ`\佅II0aN^V./AHc^hm^.E(^½d%;`%;xX&66gCfufg_gQH[HL Itv|}}[ fung>beC݁[[cV]c6E~\=gŕfidl~^mp.fpg5&gYi|>d`Aj[iVj>6~&h_HPք>` i߰[jna"f+ނ+ehۙj ].ݕVj݂~naf䅻opK~f_ՄF[clfnȈn`؞mۦmn߾kAn6Ӧju6߾mnj_I~feզl;lnngv辆~NlNopkpp`6lxo p[ gom`&& 7_qpWoOqw{>a [P&=qo!k1жcFbevj^,\0h]&hr/[5.hsf轅 :O0Oo=wsM?⎎1snl#teK\ՄƝetXd띝.fp6c+jѶk r;w~BG2V[Y^. C'u&gHWu`vbfp^Luv~6fygohsM_]wL[K`9k_cnɔ46L1c5_ rYb >umߋh̀㽅xf`>^bb7F.yh}x3Wkjߋxyfyn.yyXyc!wbz*3?a .p0RǾzdN{]{]\{{={•/?yO|x_|wo|MK|6zG||t]Olw^ӯ_O}ԯw|{jڟ}ٟ}}}}Q~)+:Ji~/~w_*֏~GB0r~92gw/*~/]1O7+,h „ 2lXÈ'RHŌ7*4U#Ȑ" CNWl%̘1ʬi ͛:eeϟB3jtѤ*mP$Ԑ5vI8r SZf괫W`Kvٳ?ӪɶͷpFkDA*9PW5|o`|M16xq!3~Lc/G6gϝ4ѓ9WzshԚWf}v/#6Lyvg[n{6DC/Mvs[~=s骇׎yӽ ~=gKxaJF'\iɗ |9_Jxjhrq} [onfaa VWTUuis}%4e8<@#dJB$1RPQYeCWb[&ԥOR:T/eKaIƹsd'GuΩg|'DQxiFz薌bhJ)i4(HZ䦜vQJ*@*j"%Ch⨫+Y*\K;ײp5ֳjE{ִM [$fj-z-;.{..;/{//; <0/ +0 GK<13/k1{1!/l0% ,2)2Kcc˄|3js93A E<4I+Y˃ cO4K[=4Y{5]{F0ep˄R֦}6kuu}c7}2/ rb߉;7ϊ;8m4ذX~9k~yg,8fv祇;5?3 7zێ;?λ7;_o~0]uSq&-35/cJï?վ ?x%,I {v R0 e-$! L fBwŰ[!l갇>!x5 F<") a?^H@V"Ė`p`L!!A8Qf<#8Oc(9ұkT "x)ik]Õ"|$$I0^FL咞$(Cy@"x)$?|%,312.%.sKq%0`<0e2l&4)=sּ&6kWlr3}#)J&q #0+ũNI E$"ƀl! nɠCc2J0D C! hB-*N 9`1sjNMɚڢe1 Ti Hl:~"dm`k#![g= ګb,L n\,.@eS%r~jJGJz+_$"\ Kn!BY0 tHWU  -A:n N06[^ "``(0A.:c$Ct8i5pM_fƍɃFPnmcҚbfbl9bnmIXB5.aTy2&qc򽆠Ѵv1sCQΨ&QvK̩c1s6k@eL13=vZF"4qR[(_Mq|!T u;&t[!#F=UT7FÓCM3;nL\7o۬6kkF,-qc3>7inoa%~w$٘1 qE;[7~|&g|Fs BÑE7v0ߨq8͖` ?2IƑ-g[R%/7<>Znr~Ǽ Ϣr =iyT\$:̹s}hA̳4n }^ ۜ1$Q`0tC>$J@j{΁}&:cF;#7LO9Xn-]Z)68S °,74{^!t̷~g ͍>OW/p}s>/??ӯ;l&`tB:&ୟ‚?O;S<U=ufSeKJUUKVC=LaTE]ELB!c&xٕSY`T[P`c Rm]B^AZXVqWXjUYQ[^ ʕYO!f(LY,$ZdA[cl)!UmVgh!ijO9X"\-Y(yr N8`pqP!c8"c\@1C1 JWVuIWa)|R!hR@ px@(ևX@ATQ2M}qX~~挘"#,4X<D8cą%T!nf!nR cTY$:$.#?*΍F&n > r!ՑVҖ*B5-fG.c?Ġ)Yq"䕍iT֋)Y ö4.bI]NHa1IFepZ}ZZcAS]dq`hؚYTH[\%]֥]%^^%__%%Dcc8`&^E&b>\AcBee D]\2@lg%%I *Uhf?JfA &m_Pզn&oo&pp'qqzM $r.rs>'tFtN'uVu^gt:'vn'wvwrv~x'ybgx'z'zz{{Ƨ|N'{Χ}g}ާ~g~Xg'n(R&(>6(F(>%$P(N(v~(縜C (ga9Bs('ƨ|(h{j X-%XB$ \(#0 ؂%D 2r AAJh6h^iy$X%rB102A$ h BZ"hx)z*xj&$i & i $0& h"ds`rRUr$'P*^juj$@Vs2#$#)srj xj (꩝Brާ6+t>+2NkƁ&@%`Β*!H*t$sVhj*ws++sA(/@r):klr*2,:l:!sNl*'#d(ƀsB첒J,Z.'z,.> /Lr ,,,pfp mJmc-6*22-՚B&-aTbm.mNFmrB-Bbmfv-7-۾m^J­n:E*ߞmভ-N.Zirn2nՆn-nf.@zirZ,^N&~+R.j^nJB.":onm*&/b.Nsnr%n n̒k1*s-jrي*`% +"zz 0:*ή.g/ r>+/z3g{ikj  i loirjB ir2#r 0yΰ&gtBqvq+1֬N"qq1q@vZ+&r.q'"-sL3*11|RZ%cj&O煶+qrrr$rv*+r,2)w$w2.-r.h0ǩ/,.3/#3(4Os3S* $isGpx36q363DE;{;oE=tRt=t>L@. C3BEFB/F GBs`f(EH4L{tLw7KEDK IL4M L 5PA!H8upȂ~\5~4u=TS|`XHhuWoTkPuu|5[\uyi6`+xu_[uVuZu(XK_bs^_[CvUvY5eg6eõn5f{b6TcO6jvAtOKP7tQvPQ JC4n6F+OrvIK؏.V0wrCwssw7pk: wowwwx?wMK67{w|7|7z{v~sC ~7#8+q>8G7[B9Ʉg$ >TxOc[ !' NU $9JbF@6Ny !0?XbFW{)(%@ ^4㏶~a40Z#1X6n:Q 9 < 9f)l jޠJi /Z'ʤLE:ooR Uz_ڶp%TSz8T8z(QغO2@:{0 EIA-5;d`& {f?ny۷ ;;cx ԻkN({Aw׀/ =ScxU%@1πcM%ZGj"s>pX > Ue~_c}8>#{GyHIT˾WK%[0F + Hyx><h8Q dcHB7F$ߌb嘚? ʿ?@8`A&TxP>=|HA E44i NDž'QTeK/aƔ9s#Ah0"m4vLT ,:&E:4* #+jY:$ LT,ZXfjv`۷q %XTػyR-\6\,b{m ]R6(riӧQ6%P+D,BP-3[Z>N. d̘#%ځMm}K^>R˙6sD ZNW;#ޕW)Aͣ~=;q/`)O:L9P )DI$/(b•l),"lipħj;p +*BFI:Gs,`#}0H #tH'X,,'yRJSp6G:rA -t&QF%!DI?q :4vR $ Pdk2¯S!HB*HO!S5\_/X `Q%VBs%hW$:Z# mv \w`v ^ ]0״eH}$-t*L@dtaTrCB *=ka p44WqStՐE@$9KtHC"qF4TBEg4F1AtPm"8'N=\R D1}8@ q R@ YC $"D:R&!-Lb IIFR&OI<:*B+a6KLcרYҀsHP>bN _C[n, e&:DH hGm~Nu悵#-3̖q:%@:1S'>P3EAP. uC!QNE1QnGAڭt!0)ҡ a$бL1z(i116`cҦCKBڞ&A// //Pp%@.n-#p&0*P N-4!M3p!{4A,n)con0U0Z/b op`POں Ð P /xa Ӑ`hcqm %-q8#%)a-cY]1oA]qQOqQڤpm[OQ%Qk00 1  n. -a ] Q 1ڏtpM!qMPQo / 1rQ"!"/B##A9Dl`R65 ;Q%'%-%1$#N"`R&&n<1'Rھ± +ӱ(2P%`oٰr,4a!R $hM A 0 0s 00S 32M0(-ur1%2S14+S44-s6/1(R3(#>2's4M5Q34y730T0fg3%37q31w3:43[N/333):4S:S75e1- <}::<3ڲapS'sڲr2ij>34ۯAO8S?dzBB<-4C!5e5s9t@3ÓC?tCtBU41v QQapr  -GmG{T) |(544~IGR8s۪ۮIIO܌tJTHIoTJ5q2ִ GLtwGN+ 1TQ+5܈PEO%8e0F a&ڄr cSMTuTk !-! 6UU/BUVGTcVsuWrۄ5VmVVq5t"dR/S5ZYUYQ5|UtVKtfTP;5 0QH#Ѱ1sPQS5VUV}U]kU-P%΁r.z^-hW&r`a v/]TmndEIdQ69 F2<f/fj6nrv$&61hohh]h.r4didWiu';|KRhW 3jjjc6JGVV-OQm\a-@BnveU9r4."7;'rMr/62r;*w]CW>tK2u!uՍZuMvgvmum7rwpww9w1xWxqwy}WywysymziWzez]z/vxэ{ {S|KW|C|;|7}{%N%7~w~~~wמ7xW  8w8%-85w%ꗃ=xMEUxV؅acxOm8m8q8x%K66jaxx؅x{؉Wxx؂Axxxx~8Cu؍+xXx8m鸏/XِYX%H+Y/7Y;?CGٌWЅG&9 ČG 8I9cy ɖquyzYٗb9qMYyy9ywٛ9Ù ٝN繚ٞYyY9μ ڜ:zڢz%;zǹ:GṟY95_i:l=cY8Eø嘨بXӘM95Xw>x8Z~zz:ڊz:}ش+X혮wXدyڮ_-[ȱ[55igXc7?7MY[W_kۃ1i;o[{ۄ{Qٔ{x+~X FLd칵"!C#lHtJX v vj3pE$0xLabڛ ;J\:ʿac|"8<Ҭt9Ja j N8e"Ma48hF.D"K *"ʚ ;E _<ƕ =:ZB+4FK$S]ahfÿenj|`>l~!>UП=ӡba%~F'vgi !n*b\J>B>F Jl;tj>$"n,&&Q^ "r:~~|>~ꩾ>~롾{>~ɾ>~c>~빇>~^>?! _)-3<5 -A1_9M'?U~a?Si]߾c?KkA[?y_ E??~,___ɿQ[_ӟA) !,~C22Ŭ@@^(#˴23N#KKf"6/0:&*+6$H ÇJHċ+jܸGCNQI$=\I0%F0Fx@A5#eF'1&PPI 4*R(KrtTiDS/& UvJ\8`(ڴ\׶sMo^uwc !D(xa p 0e2@! 0 Qk,-X8- H6‡Ó+_μУKNسkνO_Ͼ{˟>>߯ `aYF!}^Ja."|h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihtfI۽ɦvvމ]zZg t ]:h- gtNWRzgɀ@du*!,&S22(@@^#23N6#KKf"/0:&$H`A*\РC `HË#Xq!FlŐ"94eJXB,0 ! ⁉Ҧ-+@ 6Lխ8[n,@" W N 2?I #6GHS&&2))5++700(N`.a F1 ؀!*`."3Z/cI蚏uc)!,p 622!!  B >?I  9:DH`A *\Ȑ@ #6PŃ(ȑ@F6rcȑKҠʕ-]~Hd́/?^(5$yG |TIfT aH>q Pd5 `͚D[psp LÈ+^̸,y0ʁ/c֫yΞ}s4iOsL"Sþ9۵}Mw޿q @`lj !,C22KKKKKmKKKKKKmKKmmmKm njǪ njK!m;dY0hq @  a GO:jV\Z Va ,k[<[0pPB!CUp   @c(!dhȼz?~0BDpٶAd q:n]@`,^`.ظ Oxx`@gU[w@]a^^!`kPDP`{5%DAlw` ^yqTo h\aeQV{%_NЈL FW !H l $gKu0-ha_1P?ց HqMd@}!G!wB 0$iA~zP>*N>Jiѧ*ꨡ^ H*먦:@Ϊ뮼k2kjŰ6%k+ @k+t+؆+1jF1 UkXŪ:81_+4@\d1 6 0K0 WZlw ,$l(,0,4l8<@A;<4E{4IM;M@OW}uYk]{a=e Ǧl6q:wݾwzDW-vO-f+x!,22KKK;`ĮA 6& Lrz`@v2±Ym}*+ے p ۮ:L ~KWD  D0S ц | p F0\Vܰ!Mpo'l#CAtz#s<12H dļ5@/:ū. n߉z}+ D 3l' 64vÍ+}p{ڛ Kwt }X>o4؞=0#PY"r[x{OZ'k # Q$ CBnD g< G`FY8C 3>1\] K^Һ:'H Z6ɠ7zw;W@H(L G0a $r0 !,}22KKK ;"=$=%=(=)<)<*<"*3#*/$*3$+?%+A%,@&-<'-:,05/020010011101101101102215:09?.?G.JR,QY,JS.EM4CF;A?C@;H>:M;9Q87S56S33R11R11R01R01R01R01R01R%3Q3P3P4P!7S%74343349~;z8w9u3v,j%Sc${k"lq(hr.gs5a{1;021222222212121212112121212.E*Y#"!c 2  H*\ȰÇ#JHŋ3jȱǏ CId6F0LʜI͛8sɳϟ@ jiH*ӧPJJիXj5邚үI5JٳhӪ]˶[>Mݻx˷_. 6È+^̘&E,2˖3cެ9`ǀ-4q^ݙ̓˞Mw!?ͻ _ȑμݑqKN$kQ=N^;ӫ_[wUϿ[-oW/4؋/׊f#·Lb# @؉g$Тbo *`4c0ާ@)BcGp2NK-,-3/}DihZHfp@9 Q9g(e :eq3hq@ -4I'2 Ycf馹K ,yh'yx o>h=/^&ʠ/w聍h瘻q*;{JoZg]b /FK=Ӌ25~.먜{")Ixh 4m/ cVl7{Me>&+`lB *:ɹylVx"-dy*"X%TƩz2lˌB"NJ<1\wuO9 3ܭpǝR6 ߀6LڹGikfy嚡rh/J ~\.:,騧aBt-ժ.]+!-/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH=)w@ H"BġO&:PHIX̢X.m` 2mDL@j 4f#z:ֱwaxg06oL$Ȝqzc}A!!-CB T(RZ<$R _CO& (- B %0GES]$!CNRtf%]JM),ZJn3 gPeљ%zIzZ! Dh:7)~n,'+YIѳ,.MX1k.#}NџMSygBThN5B;DEіI}7IKk~=2KT. ~J} *REԦ^hPuSڟZ5TͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKͭnw pKMr:ЍtKZͮvz xKMz|Kͯ~LN;'L [ΰ7{ GL(NW0gL81<ȱwcr@L"A1&;J~Le$GX2.{_19e>5m~9ۙu=}-h;r>4ݬE߹ю- HWy|1JkZʜޱ?Pn6uG%ЭVYk#ҵ6s=]:̾-l-X>6eN|j[ylk_;:I^6m k{۵^ݍnv9ndwnn;&8 Njx<1_޷5bx=qCU|*xO~<>oys|A7z5MUwfۣi{o~]{ˑ~v6<:ǭ&|NS{#//}׼Mгzw~\q?ӛ8S,nv{Gu<ӫ/GU_r?G#l߫_Gu^|^~'}g|ww}'xx7}ζwts~Avn'fXzk"x|v~ l}wEWy4w6(tGq2H7zmHHvI8u?R*,({F8W X[XUh@xa( %Ȅ_ȃA~քZGsikpx6VpwY@xh)hw_w[>HunX~Xn}hn_׈ve/+lȊUFkwd芵Ȋ8ȋN4Z苿HhPɨ`ƌR&'EH`٨IƍBf(GEgh؎9fxXx(Ə(VUW~I ɐ 錅8XyhI!ؑypX %I&!#I*(09+wp8:<ٓ>@<ٌ< FyH?IJٔNC9m:TYHɔVZX^]ba9fyRcghISɖp etsYtyx zkٗ~ib9镆yZ9e/ْ'IYX2ؘƙ晤&vct䰚9Y yٛ9 yyƙٜYI֙ٝɝ YI晞ٞ!9yݙ JZz ڠJ:*Zڡ :$ʡ%z(",z+ڢ0/4:3Z87<;ڣ@Z?DCZ@zHʣJ8ڤNJP%:Js V:TSڥ)`cbZ6Dzjmo[G0FL9ʦzJ|ڧ R*1ʠz>#jYZzʥ کJʩꦫʪZ:ʨzI0Z*jͺ j֪ʬ*њʭzZ::YZ*:ߺʯZZگ ۮ ;z+ k˱: {슯&+;%ʱ) 6˲4k[*2! zD˳FE+?Nk;L(;G+T5۫v굀 }*zJxjqomʶTʵhJejc`]ʷT^* K{뤏k븕[[+; kۣ۠K+Kūǻ5z꼌 B*[{[0-ʽ,꽼k㛼[ۼ䛾û% $*ھ ?P Kj o˿+ l`  @ J \0<kv HG1 @:,? AjV`ZJLs;X\4zB`6Ơ P ?:%%@ OG\&l4Y2}| @Ƅ\Ȇz l bS .Bj\lhcP|/{=,:CI-P˴Xʳ\˪ M˱˲l ̹<ܴ|*Z{̸,ͼ|͆٬ ` ȑ<y POm ͿϺ̬~| [,7,@fDdolm+ 0 P Ё]ܻQ<̰ZZMc0X9mǿ @`- +IMmЋ 4ʀ d0%,*m,Է԰{ѝ gMiLֳ+׮wmykױ.-{Mدkب ؃(؉ ًةN٥٪ٟ ۻvsPʤMׯ}] ]||]ٿݸM'=~ص}- ~@-u܏ܕ;<9 KGՌ ,U.|.>nޢ0C ]-1N!.=ݣ]ܝ**B.mK^3@dmO.U.IMN>_a<Sd>f^0?~L竺q -m矊d hjln.{ 9~P  ^]ǚ d0|.θ^~ # Z{nB e] L^ ~Y.pOhpz 6Clߞ~.9`9?k~َ>__ H~p4ݔ n\;}ݮZuDכ Z ߥv:.悌;:6$@]J>1M'// Ӏ0P{FW[NjFNM<]!.߾mKgLɀ9Ձ_ }vw_$_~ n#/ƇodX/.^Zm$Oխ d .8;B?{. j7 y** ? 1ιoQbLK)PB >L0! ?AĘQF^RHAVBЂ]TcXB 1V ,3#H=}SНDe/cLPL6ANHPa Ed+[6d$gRj dȤkWKLp⑊?3H2c,1:3fB-8qQH3hȑK6Z5Dǫ]'&,s21kfsBݤ&- ЄAkՒ)[vXv5 { Ә5.^|+?|z#uh|C/{Os$i@=2̏,AY( +lB pC<Ca-aJ7!2~BkyI/U02l?O[ʪ}趜 .!|co{yyETՏT]۟_xڳ: Ȩ>(/xi*ؠ 23~">! QIa))]9Sh1 ӭ8E5x&c(Dcq8 YbX.: !]S7!9J@5DAPN}RwT)x ?HdRrC‚2L@"@.Yq$Y0$-qɅ/ rfu\$õ3tՄu]^USg7YB&fJ5oUNZg.N=L XŴu/fު)eSϨp\g`$ hu.|: OV+ т|!%AJJ\Ѧ7ifNtC0ԡ(C`z6T5.$9p ih5"Ԣ>sn4G5s8ՓydYI$[͌fɰ:.ї`5+N&ǩ5m\Ɂ_,^ W^N)u':kΕg1"ֱfihqå}aZrVz5-l֌ΰHCER?6d<\աSOO2.4)?Yٖj$Eܹ|s;ނ7k@ -p*2U\1cqK]=WdT&F]IUS-?Yfw"$[<#^Gz@B%Tf82#PNZmy,ux ߏ}y{wl#pc$:Bf]d+9;᎒hH>naU9DpՂ '~6cg9ΆU)G'D-<3z.L0bzD -j֑'j*!az^ I'/nYҕVu}ە)">d)1y^'ڄc'H˦@BOrsXބpX u3i%\*2j{%KMב6ۉ {GplZ־6eo#p7J 7[jk *Xj~ߖ M.87p+\檭Kθj=g[DG]ɝK91Wy܎K#3x!fnFfbEpL׻w8^\Lic)94BַS{Ԍ_-DA4䅭!aIo[ K>ӢW? [;ߺ>*}qO~?|7~|+͇~oO׷~}S~Gϟ~o `ǿ@?f g+L@?<=V`?=lC>``fᣁd`cNH?@AAC>[ `!;A=$TeB!$BTAĽ00 f!>dcB{A@:=ܺ`ce-P-؂-T/B0>>JI{>fۋOt2@CDDScDT\FteQtCRS T 0^dC"De0߳! dLU=5`5;9Lln{eH>gO=3ÂH 3؂vCp GG{$Mf=f"#4Gt=udH4V W=fĽ,KȅV&'HG\GtGH ?`?5}|IkHe$>k@@IvT/\HɤdJ{>[ɝTɏwI,JatE#tH,IZJۛFNP3XIcHK=KL#F:8=I= !0LĔ(}J[c 1U_Ǽ,ɎJ\TSRKe(#T,0T$.KXYJŽK^^E P=0F3DiԬlH_ly]ٛYYYYYa<ٗ֡ Z= ȭ[5mzI*K:]\+9CݱE:R]cm׵Ԓ]ݠS:ύu- : %e^ݖ:휆\ȭ\%\^z! e_";@EE\}<m6^e7^m_}``~` _ 6=!v7 <aEk&᲋ӛa!}2a}ۿv `@j _$`!Z*گU;/Ya51bpc/:%b$! K̙,2>7u^c=&Յ^ݺݕd]DV^ם^u^ud?CGFޫcѲQ&F&J.MND b(`)/긺2@cMf^[f0W !j>Fifm~c~f6f-g*fb`\ ovay~g^e_NbxnqvfHGI;gh-hrgn>|nxΐpN/9XSzv{gpg%$>bFg[$7^cbbv5j\Vvi~}&jidJveIK䬖eTd]%kEĹj=XFWjFk.dOkUNk&iYnli@l h[ j1~lelfxhdhui79"f~⒞g|hզfvh߮~fچjh&cVh޾hi6nع!nu~˨ٶmfnsvNe>쮠#;2"$R"&#AJ."^S[!5-2<>B6 "j<?>H_Naj$$MѦP&Kn>:fSR E"0Sc Zqfr&hX'Rnm8 7CJ7&$(*-6bJ%吏k7з5q2sp5&~:7*RsrnJktO>e^elkFR78NڸFO2Kg2͎+Ft6OtK7e&6[Oj.vՆr.=w g;i7p/1/?@ pѦ'*yi8vwC1?1O{? #^{K7U[1kʱo;pvrk05"7;Z1#33 wk8zs~w>zlF&Gp orl7hwNz/{wO{'gWgefg7{zwz%N@0f_GmwlA1[>{{G{zsށ|?}b|'IcdlRU?[ZkIuUtOw}Du?N}V}'~}۟?;}ʿ|/O}x:|nzLJl{bi~~(oz,h „ 2\H!Ĉ'RT"ƌ1^#ȁC,)qɔ* \H2Mi͜k'P<8hУH}*]ӛPΜJի.jUɵ+MIJvٳӪ6'۷ʝHnĻx/\[%pʾ *^l1Ǝ%C.HeÙ og[윁svfq叧~[m&[oUv.sѣNwZ}Ι3xŏ'1{QDՏ5\ 'Xux'߿P( Cs * Ww% "h("`!fa}H 8Љvb&b(1&Ѝ5أ*@=b8 id4?k,E#]Py;5\GuT|:-( :vmwsV{8c=^N:룄 k᭚w;zv8G68IjSn+yp6V*޺N~>^zN羫(!A,"#*N4(2qVb$fq^lb(qfaS80ntB6ьqc=搏~\a(@ R VNC"AH5bNp ,\'PH ` LpT8%.|C2N9"a%Y CWz2%-kH'X2A/UXKĄ,9Le Hȥ1^ơ59YL'sL!!߉Ey1c -qVie(a&)$)C#@v #)Ce]! 0p j`(Ë0a9cԘ)L'PnHUb7 հF iej XVӝZ4p !φUSQ"*TXZqQB6Ŭ\pO}LlA`X*@P2ZÞfwuJCΕW0y'lja$N[jԕnk_f/ ]o> 2YW^݂KE[ѹ$a)KC%X uGn ]lP+o&(O_ Z5dSI #'9 +d e IQ^dU;|\a,<,*7Y^3f6_ymN- +D\go+G$vz+7gXm&eK:tYf!i9g:p*fV_:V|kMt7`ثu}izvӬAzFbԽTy ]Ş]+ܯ͛U4+mGc[N0}\̈we'BCX&8? iqT1TC?Bh4`YwϏ {=`;{ _M;cG}~,Ix|)*0¡Nضܻ_ER d#MP5{Pk3x8 ʅ9T-e=LG֫j/L wޫqxX{4}?J~'"DZW2)QUUVBW _ B X \ _ j Ja]L|z &aRP  A+JG*L EбT Avx|ᴄ!ý:BP%Paar!ȡ"b( !ZHH%Nh$"O%Olhpb(Z")֎N\"@Ox"* ,.+`b"-bb+b/'b(/f"3.O.",4( bAazcZ#&Fb7"M9za;k ~("# ~8:;#:P@@raBB>>#;.dCEFF!Jb4$vdFrIJJK$LƤL$M֤MdL$N$OONP%QdP%R&%RR6SSFT$9U^%VfVjTvL2WRXSY%P%[$Z%\d\eM%]]e\VV_Z^e`fJ%av.J+baaFXN&eze^TffF%gvYhr%hgfR&j$d+(fk&jmަ[Y&oooMh'qernr6% J>'d6gRguZYbgvj`rgj'xrpzgKOA+Kz֦yxg||zg}{q_g%}N'g,,,B$(6*(:JBhNhZJb:JRjh('hnhJ(vhdhh萾hi>))FiBiJ^K2i^iji&hn闦v)(i)ri*B,Dh,)))`⁗(&">Ω6*f*)iVꌾ颂i2i{$ ªf}jgbZgR~g_t+-gp-Ԏar zgת-hm]cAHdI:-E R4M-FFVF $D^3F.&4V+c/*3^-rd./nR*FZ..f"v5.2"n낎#nN..hAޭnӂ$A(m2m߂oJoЯ̣@<.kAۺho/i /oE҂aavm%nsomBFV0s{0odp.p#*3@5/ +L-ALk:$0 9338 8AAP@28Bh'q@'81HXP:x6F۰VTÁ8C5@B8G1r'#֡ M5A*C' T12CAX[AAHG@@ x@Pq'{Grr2,Fd5"KA%C%grspӘ 0<33$ D?AA D Ap2Hx  r:&@30p3EsAx38sA3Um}4M37o!3rBWC#'/;%7'@ AAsH3:(GD:H!cBKXD>[4F ?GEckDnn /Nc06#Q2OtR uSoSP_.&@J# DC?5#BtE_tApAo,ss;DHt2A\ UWO?54bNs.UG5{^2P:CXK5AD r #4oH4A8t|V=FS-93,:+2slA75Wmi@0vT-ZofǾ0/@0ChD h@qoG@v,rw;rsx< >[2A0"p6K7u'uh/U@t7X\vmW'" 32W'@1duIXuxkqlh2{:2OR{+D|u5PvH'K/pKEku4 dq 8s+y0;ǓCGKǔSb9j9r;9CyKSy[9{9uT ' 4. ztG34g6wkEWv9B#GM4mrz@9k9=﹟3zк9vO4.A tPcd+5`T_KUCQ^RWu,5t⺱{ˢ5NwNg#BT-[:czzmA{9f0oﷶ?G|;z|{(|'<<м0;̹@A+ >Dҧ'S,qDp/:y/r'`wALyoqqq; @T@D@2(|'@ w02`O|~W=&"7#$WA`2{r}SG4)wr,2w2-y- {K2xB@Xb[i/wy9Kynh|o3X3r95k{w3<2C @.x:lx=wBP3e/vLs8>羙? At@Dtt+Xtwtu5\l=@$8`A&T!B@5'9(@ \PHfd`KHSMN\ JX$Et)`О?D(PJfLt(եB}ZukցVb*lٮa hV,XmϾmYsJ .Yy+_i;a2rak[F0#O4WbhRp?aj "c(LPAl!DH)B /P'Iį=ȅj-J⍠8.;,"밻  < Ql9ȁ@H@0?  b#/ *A F'!d7t9;=% Oft`Y4L4 8B#2/HΊA>s]h]V#.!F*Lǁh)UbmdO\Y}hMQJ" AE+Wa  PG@UWIPWWSMvOj͐~-` MEłs!g~h`/MA0ch7CÏEưd I>P/L\~9fgVo.9gw倇0茏n8iFi8ꍧj}6ki~kfN6lcƺh8NmA4?n8]yc@V]|V}wpp<3q:q8/@Z}H9/aIA6=!( pM]!ԛ=da=wwi 7yw>}O>C bdC{k/kg}ʏ{w}Ç.|п_g>я37@0~@OA~%v0  )y⭊`zӚxWÑP֚^7D iO ăyD$.1zBuV]n8[ahLܢo(&qy\4XjqgFyyrTE:F<汎}fH!p \ IIF%1IMn$&+IQe(MJUd+aJWƒl-qKMR%/}LXSŤ%1LQ"S$3MKBS 5Mj2f2MeS$g9q :D~`+@Ite=K|Ӗ1!f$ !kL`&P0DR 6Dy͌Jӟee(Y#fXcd5ʂKF?xXg@Szi*}RU,9! !@$ J7-na TJCGdYP:LUFI1i@YjWC.j+:sZSDkZwX_Vdlc'X˸$3Ф!4 3XdwZvugC;I<*SV`d\Vq!*W\-\EdkA,a(s ޴:d2U솖 D: 7!B72/{"`'\0O`X¢p` S;\;Xf0aOX.[LcX).1YbӸ0kcCCAm7 %fTHb$$lH֒H/d9Yn|g#yVxk&ׂZ wVyi;۔޾7Ňkܨ'I2:f!(c&!ұB-_sߙnJ.m֢c.?s蝴MrS?:YN/s|:?Ϭs::NϪrg-4ٴwl;vqt~v[s;Gwm adĿqqtx?>t\1|,/8{.=Jmr:B *`X?z~m?7@J/v |^_a|D/%@"0P'da aЃO{(LOohV|Z PP /}4(o4.''SCBO^o]Pw#d/k'FB\aAVxi kp wh e Ͱ p ' 0  ph0PfP pPPhfb$pp`ZaU胐 LF E ( \P1d W` j\wZo >80M{%*$bECJ? xD;(R@MfyJohC;T `d7~FZ9$R~El|K@xQ g67EX#*J @w'Nm~(0,0Q#=PA/c;$Y2$g$?M;7PM$m$q1`$eW% %(E qC7ҫ7rQV9`b;#'tP)i2&A%)Io1r'!Eqe@pDGk1JO>Rgg gPP Q%%&,bqRRnwhw3-űKS-0E[`"EkS-w-4 $1ss4uS2hj9:F::9jSj` y9sj<qS>PAw@32A+S /tc33kDKIDMD+EUT,4CsC3{>~r''OP,5P#ZtfrH&H4GGmIVtGUr4,{Gr+3+3()I#tMt)2KdBaT;e00Ta"#ZNk53=95U>2s 9'5:+u:NǓ8/:;:?1C;G<#5KU=3TES}@Q<7?O?eUo5@s5VmS=S5>u>SA&bR!%!QVK&|*6%V fXLoK}W%` Lj8NQ|zQTV7&hxLSxu7*[$b%ӔLMIIᴍ [q8N *@89"`dm8LRrsWs7lDl "ʀ|_uo7"'x6nSx9?yW{v7Sj bFd"Wyq8ϱA TQ9PFyWK6%G 7 0#1Z5w2q9~m P$aw/S0myyLe2ٜG71nuyy=pkc &69PP WU[͘Yh#\*"Xh/ە$u[e]`7`kn_16gg]wZ7v5}zI"?6z_?_GV}YW}5hnI ֤ dZCZݚZbZ3XKWwc4 u\կTEձ)U-5 W3۰ߚkV?AV{u1l,3?WQ)ScX_d(S%uAշw/".;ڳٺKCQM[["[ p;GЎKXMԍJɛ{(ӻ(t;'x]Ҿx;\G۸ݴ |IsG{WY[Z;d?hKj;Z;UU{UX;3ܵW|kgwE?s_y>/_O]ud8"0" <0…1|1ĉR1F7zr`ǐ$KV42‘*[d2f] D 2{3(EB>$j4)BJt ӨIR-jӬZ{r+ؖbǾ4mӲj*۸h.ƼzW&fhĊ6>$'Sxmf/w\ϑI?6bՇYvkۮMݼ{ 9ԧK={tܻ==vq>~o×6}\{ E(wBmt 3 3?F3BXS  } '`'iTHC5C!hcmXXc.ê|44ms$]UwrNb/xamfºηi+jFϚqmsZ! M|FR̅(L2W:PM0YB0 (|FBچxJjsé=ACoX`;P P#O~ːd4X62 (( BȠ` lCF01_\87N㗱* %<G1Q$xH8zEP-hI~4dIGp&p@J5IbQ7&%IW}\BkLaz*oJK~60i>F)R18h<&&aiYjS-g11$mh0yJcg9GxqaLOZjҚ6yQ7Q$k0N_~M=3N~4Xb%@"׉7].i M{ GX8fxa,l/DJ8\4^cL~r7DRa]^r'FyLvᇂ)2ys EA!O#%sH`e\^SV'ֹ{}_.g0;vKn_;>]y. w^}G<;c^o';p;nzϣ/WѿW<w.ۧ}ܓgbO_zX~s.?Y;`an*tW(s@4@'s(8 H!؁rX*%8&/x)ȁ,ǂ!q*6(BX8؃(x&ζ ׄx-F8XX>8S Wn 8 ;~Ki PX|@-Iɒ1}dE~~ (DXT(S AHq HsuiwIa"a!B 89%@ 0؈xp~5!InhxY)ԉ A qٗ@R!@3Y6ٝ9)vIi y 剞p߉ipq 9j j7s VɠڠLjsJp$(ry)pH#j--ʟ:э9WɢV/rYL:$YGiH G@yQW%ɥ'7I O`aj}YzGkp:UfڥZZl'q=|zc9[zw*~ʓ*RJǖؖIZʩǘ ryBzY:zFr Z:칪'JکzJZJG pz˪:Ŋתڬ *J:Zzɮ Aڮ1z.p``{؇8%(nx zxMyP V֌)tH5B+@5k79;˳=? A+CKEkGIK˴MO Q+SKUkWY[˵]_ a+cKekgik˶mo q+sKukwy{˷} +Kk˸븏 +Kk˹빟 +Kk˺뺯 +Kk׫˻뻿 +KkNjɫ˼ +Kk׋٫˽ +Kk狾髾˾ +Kk˿ ,Ll k ? L [K ѰL%lIK !<'-7 5l)1|= > = 8!7#7,=2B3C7F&0F-/H00M54K:9I>>E>BO?AVBDYDDZEE[GG]II_JI`JJ`KKaKKaLLbLLbLLbMMcMMcMMcNNdNNdNNdOOeOOePPfPPgPQmNSw@ZNVmSUjTTiRRhRRhRRhRRfWW`WW\SSSMNIIMELLLMMMOOOQRQRRPRSMcLQkHWqIWO[T`{]chdddddcdd``i\_p]`s_bvbexccyee{gg}gh~ikiqlommmnnnprrsssuuwxvxwsutrsrppovtZ~}:IftzsqѩߩݼǴҤH*\ȰÇ#JHŋ3jȱǏ CIɓ(Sb<ƲK|wb?m)_8qFN :r)9[Q 4ިc(5kSV}(%XcU]˶۷pʝK.ܚ)s _8 :%6`U*7 4q@6YdO$ װc˞M۸-;)dvSVTeH!N7墕OR3ߗ`)d9}M1a3E Uqp:6gRvvF8>DI#j1b #G2'1p Ɖы!sŃc=PG\(*!H$.ZaN3I2RelSLyt( ֚KэO.Y^ F4HLz\#9"5L5!$6_y%YPI!o\@5!W6چ9u^x$,Rm3N4iL`fyO쳚6f:3J9 N3\9J0i߸ ,yH޳CQkÛ>=Kjm#=.ӝCP7D3NA;?# 0Bbp(}\VTzq0NfXX֫2WUXr4Vs,Nu[)E5`զ/ RFT+VL&I+^8ʂO5G:JĮ(M_)#vLM9Kzj+„H=ɡ6M]y`֖y"Glk0ahZZ\B5",U#jiD{ԥ*/ޣԲMf*ר7S,RIXu7Em_kVŠ#-sKQL?լ%"ZRw..fׅHv:6Qpk#mzgµgKDd`i^VU@\T[yo,gnSPgQ+غz g q0XVSHG ؠ;d\, t⳿4li^lQTW;8#Mscl>Bμu ͺ# IigMsc.K3nNft_nJ2b7.ēJ/ÖtMQP=;1v j[["8$n㶽MrNvMzη~NO;'N[ϸ7{ GN(OW0gN8Ϲw@ЇNHOҗ;PԧN[XϺַ{`NhOpNxϻOO;񐏼'O[ϼ7{GOқOWֻM=w>/^{5Џ埏>K_{^}σO˿yO/Gt7G^P x8Xx؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFx_ᰄL؄MH@RRXVHV([^]Xa8YdHgXblHr8rHՠYxaXX}oHTᆁHp؆Ht8Xx؆eY؇HJ脢PȇxHbhȊd{؊`(Z(hЋыxhx8'˸8XhX8NXSHjh8hb(XhȎȌhp0x؉ȅɨXո ِ8x@ Ij0  IY9_ #L( 44y8i8<ٓ>@B 9HIY <=)7I \Y^`PTI7#9)9sPi *imjIl,y/ 7Y9)UYQ N9铆irșahRٗ m@m  ssٚ_@_ im9Y˙ ٝՠ9) 9YyYɜ!9 `` ʠjz YL谏&Z&*,ڢ.ɝ.6z/(=2:B:DZFz;z,ڣ78Im` ` V :z][  !:J jlA:lsozxz)$ /pS~|0RYʨ jڨ`:Rdz:*zڪ :Zjjz:Zz1qp@1ޠ姒:mPJJ麮 IyL[{ ۰ ; ;[{+ j p Z  Jph0 ZV Z? D AG0R[V{XZ\۵^T[ad[f{hjc;a!ъ*`J+ ˲+p B;diWk;^ Ukk;: K k[Sз1 uv۸67˼kppڻٻ;[{ 뛾; Aq*{ `&0+jp` 췪`.0|| l "<lp*).02<4\6|4/8>@B^ս ]zޟmf   z /֌ޛ6>^A =ԠKf4mA>Z\^BbnNz!N #N}7 0 o|@v }5Le>zڰl̪@p t@ l$jnBDFgtF^ߺU^ ֦3 ̪p n 0 :Ԡ - ~^.ύHήЏ"/Qƽ̝,]r -`ZѕP٥.zeՀC_N ,P`N ڐM @ @ pYFp ` .Ϻxo!/M~&oV.^v m@7ѕ`:t`  Zͩu@tO`ͺy  P a >Qp  ɞprwy{O?n_Mn  ՟=P]p P DܯL}ʔPB\\QD-^ĘQF=~RH%MDR%Jb-]t9lXl^+X0^aT(TefST-bAi3~ӧ8|a˖  >vhevt7oް|߻ KMb}6dʕ'Ӈ.;}ZhҥMFZj֭]6]qܾn(janݹ*Dd8oTX7<ؐ#oW^Ǟ]vݽ֨2ijm*)]<}]2 ceVejPP!F@;8J8y[p㍫Z꫰mqr9'o)oQ'8.kq/w Ҭ1!ǰ$% ׂ0ئJ+TAKCLm 7̤72) eaǓ!\B Dav| qQ4RI'RKC"&jiz)>b Xa*\I%[eR*Kw2|0KTgpr3ƺ S/²F% -r$l2¢, Ly祷Ԑ!_}C^1ië̄x;ӷvb鄈N?$iZ@Ü)A/ee_9f o#5Z1g@L&$ 40"iT,A$VgN8i@RHDLP DXUP ETag`B'm96YI,wQGtiZdFtg|'L$-;:7s7眵D4tJxa3Sa7`5f'b9gdiHEfG>y嗯/pYgٔ7Ҩ4O6NCULT#fUQH4Z)ōD4)5i)D+`7q D{'8n.`5AuЃ@`(G9' sCa UHϑ6ۍԦ!spLT" YH4qw+D<&6щOJFГ^|4l"0F$04IH$bѴH C[q[<hZ'A\~p0$J" :)&ac\]):!jM;rM\BR0+FX9T.+e,eH\52KoxB!u(xOKv,f6D8"x4LBYIF+T +>*! 8b< XbnX/V 61-9rcG<uL˂i)QIJ\Ԧ\\)Yԧ=-L6j vNYBnv]v& h;^AfEozZ׶׽u/l) "yPgp]p0֨5"mq[V$芮h;a WWa!o5 Kq& _Q[Wj5r1ʼnٍ_`y+61\L2F a\d:y]rd(ٴRr=|@1p41FnQlTd룳ii48ΧF5eygVo~/MhZ!qL_+uQwԕMu|V7u2[W׆Hueaw1|g9Fwսnvw:>MmlVvl _3u!sle7kG%>qWx5qwy1o}R:bAlލ87o׼ 7 >T@KǛᦗ*v(!zz $d@Cm>vS*V o{w]s;?wwG|x7~'D%?yW|5yw}1oG}nP ް_}0AC&t{˞lAم?|l=jP*.|7χ?}'_Gu$?~Gտ~/}\/e*Sooo,ӓKГ>Z(t? GY"l aS>jch|黾tAd> ?3#D$T%d?ްB(8Rt69,K> _P@sh_:HF+q80+"<Ԏ/&9@;{[[#DD$GKL yyɍǛɜlBT|WWt9C20TB/LÆy6L4cHʬ/H0h<>AdR-ы %NTSK)RH9[R'S *g CS B5C5J@EeTDUF5{hIJTw18EL7PPMSPtS8u:UU;WU=Y?QC^98ok{ LMU̗TVUUuUXuV+UZV@e\9^9m`ŇJIEKq%sEtUuevuwv0sz{|}~؀e=ŊHgEX(ieX,V]%8n`Va b}iByxP:: XkքE؅mؗQliUY9#wؐ5RV%YEٔ%eYguYZk*BA,t$!İSa"bHѦak5y[cL-Vw@xTpp0v`Xxu(ňwOx@ܒXZ=8> E8xE?>P':;Pک=ڄڬ]Z2D5H+[MۿN 46]ab5tۋpp͆tyȆsx^5H܈Y5?؀EܒE?:#6??"hӍ ?m<}e`>[FXjE`BPEP;UXEȚS02[8SXaEb^VP7PSpS@a ZX-h^j8^P܋(8_ܦ-?80\?8+`6`Cvn96TH > 4V8TSEPL`yڅbFb5(SX5Q`B 6XFh::P6XPތ-ֈ80뒇@XZGss(NzZF yEň)*uhu@LMÅe.bw.]JZiFk&SX_-&\@n]Hlsj8lq6c$$>bpxEpxjZxlv8pPf`抸n^+ktX>߿ kMkrN8g95?uv `#("7u"u~0(?`>F.?:HnP"P: ."`%? hfղc &I1?AVK7h^06[(5`Q^cVi!Qn]aZME⺆jpjjX.eU&k\j wXVp8l8sތ`pa(^a] caP3^I/j0QaȅqQXqacX^@]>j0zokl pj*lpjjurW^'Hp&vsu ĽEMW]sznkDU3>Y6eTh9hh5qoTE8:`cK#i/`&a\H^jzpppXMzb#._^r>#sYjjcGvtdV>vp`~z}k^wM:v\^#jua">rjYx8(r`Yu7023ψwsh7Gb(6:z=>/CKE[ͯONo7l:hi)c4:z-~Tg:8.8Xl[8$Nu$fuWp2yuWp(tx(shE6OcgcWvfwv~v~8t`k?}Hmeov%Qsccvpu7򅯈(ow/2kB!+r:Zww xd{_w&x|'Y\^cF90BAPgnnh$V8.E V0KEyM'pzw\\6ZtXz?~ip/燰stxhlfsP{y0 jeup~\vGwQ ]5s™Sp!pQ\ Es| Ȑi]5H.]DeKgZ;'РBUҤJ2m)ԧ\\j*֬Zr+ذbǒ-K Z0jׂ FKpE޼z/`~ 0Q"g82@9(˹08{LМ!mBԽ^sl+dUQ *T: J8!ZxIh}Zn%]uE_x"^&fb1d?QR$H%;#AVF()?VRg eW!츈&enVJ*骻{/x+Ig '>hhSÕX|O{1lr%'2-:1qF<"m6 Bt X1w'x"^+o/bl-Y ЂX48p|0m, WxB2 $$E2R7 (IRRB Mr_XbweU^nAXO/gTeFTѕL# XG<y@;b=d& <#@F2Dmrфf5+)q:R^'өu `h(6:rzQ,[ܢobr~&6 Yv04fXL2}.͆r5hFHΑӜN$;Sҕ ;(U$c+q Vs/C# ZESA[2ԣ,!UfbSu4AѭrBI*V)(e)ZӪ}jYLsE`_,Vdy^jQ/{lڢ0MS3-ժW366a+hiJڌa 4qF6w f-^QEinwKc(a,$9K1x.t\cKU.v+L3-+Sz񒷼<6"_ `h/^k\}c̪R[ihu.V~i6`KWoclSrW 킕"^ӸƝ(7^,!3..;a'w~,)SV򕙬e's9-&0tsC1c;vxn1L6v;^/L¡y*R ]a wзH,/K~l1̫K,7FΦ>5,W)Oz泟hA):>!B%41 aoVմhluY5Q-iI Y/]X+PT]7*,ъU.*y\#G<.xC?n38#.GטʹP8fo6C~/`mY h^^]kE5 #"  >/p0:5lF:tFqq8;Ӯn;.ӽv;;;cg\ٞnR/^ }+,X!yVEb?Al} S j0DR8ґkd1Ԡ5kXaQuT?>3~|W4>s>/.Ƨ~8/r $9-|X$ʭ=QP*,`m[@uQu1+BBA1xA:̞>>^5 KJy\M : BĠ !!"`Ǩ9LN78Ǭ7CY?!9C1_)p!V\,EB?P)x^A^Ճa F ^ ֠ޑݥ a\ݍ"))"*]5 :`7LD4C7t C.v:5܃::C>ڟߡa;7=!5VI6>q@J>D=Pñ6X_;CxAꨇ ,r&F n*aAA'v٥]&CC]CBdM$YDFn$Gub49|79\4bb//b8b2v3:CXQV# e;:DP \1ybD=8ƶ9#K9/j<ue=";"C..$$G-E:$EY$F_ &G&bʝUa67PI^-.=b. f:=c¬:9_Y2PPQ&sS:%TT UڐU&Vnև}>%Pܥ!Rf1c5^_&`s&t&&uV-&(&FICdRIL9HebfC2FS9|a΃8h e4 kgR mڇNܦfc(gf!p0q/qrr^RCn=gCFC臊(Dhu)ag%cLALadJՃy3C=g58cC>5Xƥa}&~'NʦSIha,Gn|闶ÂB#=hH0?L(!%gC]he(%%C)iD~t(a7xgHHa8fփ1<9’6cgMlZim*v-)iHɑBSЮj!F=Pi_F' f%ꐞ*]b5ШH0JPL3äv=X8c;c9C9C}kj^iTԫª%Ҭ.0hꭲ^S/ R0͑ZS#-t*ks:k)V+ʝaCdbP7H4Ԩ&*иn&:PX{^j>n+}v֘6bHjb+Z.)1l'JlXMqPkbSN:)f~첶Ǟlbj@j4Тg0`n38C783p!0{?(-{Nny%TvU*F]Z0n ](Fl צnضvmۆ(t^봾-nۥ"F;Hނ4YI:<8$?T? +voiYb.nrr.znד.lqRZ,krP(.ɠ4 jFa0eRj=0/Ꭵb@K0ZCBnPJfpZ/ڦnU/p2&T/.T55lŮ02R/ װ'9H4\dCO:.? σ#9G|>3Z3@@4A7PB'B/4Msw'+q6}7g3jba3WC4I7F;##sK4LC?OL-e1&D[3NtEwFptGES;uNt15sZ-KHh,eGEtAh2Q5[K6jp7F 8D^;Ug5WuXXafZZ5e RgG+A1AdE9HDuXXO`Ww6b]kt96ZZcte6\vfqLȢ*;IgWčI{#bbv<`5lltmOsYq@6oKoz 97h/Oi}w}hIBLy 1׎DV6LF9\}v98;1CfolxC88N~w%{p'2qqWMt0H8ObPaC#kS1m7/x_3PPC[z︚I"nj6w\ES?S+yb0><:؛>~C2*YntXuv򗇹ИtyStdss9s)%*mwRkvNqC<߹UăIwz=l<=TyUyg:v{wO:P7v3^7;HDydhعJ3#TA:9|u݃BHkp;Kбn__;CC:ùcsEȺb9 Ica39:"<(<nb{{|{cykz̛E|_GQƘeGTkBV"UE<뷾>Ǿ>׾>W_gOl~_Eo)ACTwH??GO?W_?g>W}>zHfVDﯿyO???@8`A&TP^D81":sԭ[;w'O=QTeK/aƔ9fM7qԹgO4Mғ/wq\.Kx[8jUWfպkW_;lY>tiF<I2Owջo_ZhvK|lcǏ!G6lcp֍\o߾>xqǑ'WysϡG>zuױg<7vFXmcv}{2~}7.ҿ_(^ 0@Q,LP\0!P )T 1PC /A 1/AKQLk`Qi%OCGqC!,#LR%'R)t+RK!/0Q@}1Eqt7S9oGNDP+ TA -C B DTI)tRG M9SLQI-5PPTI@QTs6ݤ[qUN;qasae@UVe =dvYgVkKZl[=UcMZsM5^sԓs^9ɷ^vb`&f5_l&ч^8چ8AAbi1c+&YC5Us\rPWi&.Yg˷|E}XR%8SՒidZkjPNX;Tun; MlyǗɕ(gsw=iePfU/:s7s׎^M3 Qr/w<]|#ρNV ZT8COt@G-jI0D8Jřpp-xF)aNjHTݶ&RpH7>O<󞘺Q|!H.ˋ`|.jnGKّ&HOђ!5āe2nLSXDΒ&\ W e%7)ʷ}ϔ0EV: Ӓ2 ky,$EME|Q-NW}:ПP uV XhCP,TҹN., ˄ŶeT"h mk*dQʌ*Ⓩ>Aͧ g`,\p~:hKIRk ]hCL=UESt` m6WȂIR:5 #H|rᨀjZU@aժYXd:SNUl@kJX>]m™@t} Zj%⩅!e nHj0X-4Hg<سbkg9 U|CZʇV *$um, {x:6nhc4U ŕ=5ٵvUe\~̦&]ϷZnW58Y@0+j( )bK[-|5 kRX!TnX-F m`pݷiUiYrEdoܵDmR$,ٿB m,%9b,ĕ^z4)I.|5]8XpFF)8dfNx+-jX{p_{;$(S^ :,ÄRtH3wҔbܜ]9oID*:Qԥ6ujUխva qk]:ֵla6]ldv]$ն6!im 5MeճVuXѝ vWnyWU=oy; n~M-k/<ldW@o~gArz%7׍rx~ief9%E$˰l4twl㠣u\1 7ߜrv.uOձJd=l Zt \Ǻ%nW}m:\0vY u>4nS/) o<w!yO^C('oAx͇7?zԯo]4$@xbnݟB'3>P?e\p(`x `  d57` 'I `CC70D1PEQ4 KE>EogtMqFF3Gq~4HH$Ɠ.+ ;/P=T 4QMQpQ'EQFM1N>:Pw``65#tƓ€u HJ1KQUWՑWR%PRAR3XdSSsIEu4 PUU# Zg \I/tq5 }5 ]U7Ŵ '5MYUF5SsT 8ChHO_B~CmVUו]'7+bQXc^0_UN\bSeWe1c]3^dm=DY0Ov(g6h6h]`hh6iviih6jvjVjj6kivkVc)E;ol[#gIvgMv)Nm6nenn6ovomo7pwpo p7qpWo fUll)W54ym#]e;s?tlUrkrQ0.Q6LcKgwK5p7wuwwy7w?tgt5u.1  2!!0)70,R/.r{sr+,{/.-×{}.r||||~/W/W*|2)ow!}z00%7!!S2)2737bu3u!vQTMIԆr79V9U}qԅqs3tKTxtu8l9vrxuB㔈OֈoՋx>ivxkŠ5sv8 ۵ecxcBK]Wx_cؐf5rt[OR7ىyIY%$ 37qx=[9=qx]͸m9"N94ph|XS`}aiaUE;_7cKm9;H3 5 -9yySs?yXy3s:3;;sOWeA 9ў협ّ  AGudTG#5g8>::9iZ+R)15{Fڅ@ >N%By`ۙc:Y5agkxn) Bi 60aڙԪgm:-:٫x/Sy C!1Wz "D? DAppano@Dr(u!8 X'؁AxOG{2 x뷴ei;[;+-so۷|MX벂M1X?{w`2҄14SESC5;GTO5U4U:JsZP4ɖػpO`̛UЭڽؐ%:Z}:95mQ9G'D&@BK\B |W|O@OW:E9F3\9?u):›ȕɏq1wPX/iʱ<˵\[`˽<|ɼɼ<|\Ѽ<| 9x/܇|=Q5{=1Uϭmu鏠=APF^o=y|Y3Q^ÙPIj7}~ު-~~i^IڽRf:yry3}8v9ݞ P_Q[[w7__*a۴e_i}mw?m[?~}wtwDAAy <;3G`i~`  G}J  n3`4n8Ҫأ(*`Lԟ$I"ʕ)| 3̙4kڼ3Ν<{ 4СD=(˔&4C1D:xufC2$4Рڵlۺ írڽ7^t 80۾ ><z[,#hi}qGS*O.]4լ[~ ;8O7}J2Xݺ`n``aH(p/ߠcKzs~+oa[ƹ$^ 4K=_ itJ` .`eSP omua4vP"Av5^y*".2v7!ȅQ8`>dBIdCF^pa`]^DPP^pw^1e][v xaW7$%cKF grIgLHN(5yٓF؀ C AtqmC< JCD0 7lCyeI#&i})ꌤng#p8#fJku'z^ƧV~FDpc|עDAC<0[zqש*v-y}hzk *۞"ja@;K0-AQGDkCPjVk`jjc&rʴ͛g %E/2 DL0pT+1hěN]vaL7-}U莼n*ou^(MʮZ 4":|7zÕ0vaPcwh^TSd5Ar__y*ϫTM$.k~_QԆ p(Ec$!9({{DGKJ<ߚ깒cݦOO}srR0xu iYc0D9A0н ]+%l DliJA#I7tt z: I*4 [H%X`VZ*f<r C1h \@)II"1qP!q[An QqHK}1c,H BtE)XG;@-X;1$? 2xdd"hH=.{L$%B#$7yIM:ҒJS",e*SW&k[(K^r\0a SdԸF6LSXո5QE48. $;01(sRn3&jo*"Ą+ &Ѐ tuAЄ*t mCjЇJtD#jьjt(GJCRj!KډҔ >,Y<>" ۑa }B%c Ҥ*ab4ʆ Gs 地;iu*@Pj!*5-ulHcޫT: WzW0d5YEô>-)!]E#sU]WFi-L_ ׫6z 0g?뮷$#ml.NcXmm$Nj X٨F lt֣mnDKbt]K1fWb;:w=tG"]Rױօ,weYw. hȈH(H؈(bEfvHdzEt5芯.Hh(苿(hǸĈhVCnáv6kSU Vf `ׅnȆ+rmjE8A,y#1 t!A@^DCJHO Q*O:E0WW[ʥ]_Y cJejQ*gkʦhڦ^Aڣ r87 b}vע ʧ1:5vʣ@jCZGJi`m AVSZd ʥ*jHJPaЦqjtʨ{ڧ1J B}z6 Uj 6A hh0@I`ڪfڭ*_jZ* m@WeOzQ HZ0u+j4j 6 Ѐj A`h`f`A@Dhغ:z% $k)(%pDe2K NJK۳Қ@ rJ6:KM+ Ѐʨ d`dq`` *ڲk{lDʲo/{P H6A3 ]K`KEkzI괏P; V+ \i@|uf*l뺦 wF Z~;d` D˯{H ~긐 CitD`[ c TPQ=:]b@NZ0 hD {pFۿo;LF@ llp l<D ,L| ukDKy7+A@t RWM 8 Mǐ jDbw@!ê πߛ˹kA@a뤂뭩륰Ju̪xlt<,ONƁƃ\ { /ŏ*{ q[r +{dpU)lʧ<.ʫʭʯ ˱,˥ʳl˷˹ʵ˽˿ʼ Xpv ZpZ |d2g0(ͬF˫ ۬_L\Eh OJ smw}uW փbd lGx lؑMo]ٗ-٘}|]JM_m+-rJ٭ԓڱ}Ӱ-۵ך ^=`r%B8Ǎɭ -MMm٭ڍ -]i _ܥ5dR]m ҭ -팫)''eMN=nbn t-10.5>➆ %'+{xZ+4n1nGI)z>nx[E_~O.ce.QSn*YV> nH$nu[G Ӥd Ovvt~y^I Jĩ.dP@^鐎蒎ܙ~ ^N駾.>ꫮꎞ.>뭾.~.콎~N` NR|X7w.^Gs>vp@~/8%&-fhNm~j~l.U]NF@ OD!>/M8긞:9pua4]",K,Nޟ 'C,S<2%/j[$;FpϘ O;aN״[5c7zC9ceYf-jyBJ?vg{|VloevxhaS+p-1;1##q#liPz\b|^6WsFpL G)U/ӚTLrsUOl#)c,YYЎ+j:¶+a_WWԴ`6Պe+FftLbh\9%4:(1(ZRe_=0l XU#K㾗on ss_i+ۮ V^ K@m>@]l.~X"ei) p],{ d' EK+-6ˆ9EC6ɑ4kLTuڋ|ex.ocd'9̖"CovqTof8'EˡĊ!qgCe5[un|󆵼}UI .E4ZXHnDPݷ[wI>;89q|54J^6܏ B?ݯӿL??ľc$@@@@[A @@,AL < DACX@+ 8+Cx b`cbBV;k *:p>c` 8C1C2}2C7HC8:(C9C:CCC@ >4g:`pb@b(b$=)k...|6?i8QER,8hC|CCSE700tC8DYœDZE\t [E^E*CLD@ZDFbDbȹDDMFgB UFT Bn>5BFSlR|EkyÒ@CEx]GzyG|Cs78,hFpb4 SB½sFil<0kLG 8`l:xī5dHRDGRTGa9h6x;|GɅIAGA@Od2I'HPUƎ5P8е?Ȑ46Evd8h8IxIIJ @IrLHg;J$NO$SĊ?h9:4 p0480lIRK5JW4ʬdK_LʬʼLDItkKKRTIJW >J880L6P96M8(7 ̑0LlH6ƄC˜L^\NË́8 MĝDT?Rv$EeXmYUt STJU:ʱ5eTTńZef[EU"ЅU_Ui &>1F8jHWj8pDWueW>S?yBpt8hE>uW#>BoU2(eXEMX3yX=Xo؍؈ُͦUؐ%ّXXXUY%ؓؕ˜X5Y%ٜ5 Y%|%Y5a*LmBV.LSdC=C]4TgEڒ[%ۅR֨m=GE:7BX[#*A[[[ \A-\=\MT\m\=\}5ܷV֡\OZЭW3@{^^^^^^L^^^ _-_%=_\\1%^_5_n_6r,_1 ϕV]`ak`6`^`j `Ϥ9(K 7 a\3q+bU6~,`>\7Va7V3Lb aWa$ %^~s9\&b+3#aW+<[R+/~&bc26c(Fcc +~c_1;ӑ; 72.65^?F@)~*cD 9 1"P`z'cqd Kb6nNdO~,V^V6X :8e:0 qh>Yhz-z^pnr}i iigVih~g>fv#Z‘B+ ,jjTk%aBp| 8GxP8/P00" 88Cf.F:=59L;nlf{lɞllʾlv:l 9d!jі KO=;98G:k(볞!5 "ȁm"KGxnJ/P#؁RVlԮnnnnnFmKno.ooNo^oFo:G^*Җohpm&kT570PmJ nFnP/8e605=&eAqq \6doP|H0/8 c$!Rf/HGqrtk9qnd/Ip6f]#rlLjcqe-q183 88F͎P3nMmn:s9686T|/F,DEw`-?q0u_i>d0d56F_MNGrm jtZknM*guuu[ mSt11muv~2ouDFtuucuue_\߲]wbio_ua5jGqkGvl7fdwrw$b7.'u7VDf^ww+H?f}Ajvodexxxzu֓hI!fj  gfjFi&ꕮOj.oyw昧yy6p饟OzfzyVz/†&LzV?FZjf!x{#taqg׶q7jE_;=]ɟɏ|n_:|ܭ(ڵ|G xM|_n|=:T΃9~`/~>ESev9eG?~x$P ;>#A:BPM /b`#ǎ? )r$ɒ&OLr%˖._Œ)s&͚-3Z4a%a8G,HG: B*!P=}bͪu+׮^>Cٲd-gS̑CNv+4S^9|~8,a>uKHN_)LaG*D+E8m-z4ҦONg<l Ry"$ޚFt1qXf:cq+o9ۿ׹Z'Fƀ!sJ#?J,g ڨhS:*?Jnو Š Z:`.)fJc4!,n8,R(eH-ӞV,%ǎ4 L0,t+\"ʒn *>zh"O fV8 4vNH+bۦQ u<̢*~Vl -|PIȅ>Xǻz*?z+xGfdaj髢ˆ>X%vjjꮱSS]O,Du׸Hh-L@ӵrX5sol~m2Sxsr; "M4x c Mǰ;Rkb/\ƒ269s`]k+Д}Ljp~5dc-*)-fK*? YӨiԘjBf D5ә,a kXXF|bm!9iB-LxN[ <bBV 2-Tq!v>iMpFt4.VuӃ hrY,j]4KJYI:6UhuKJP[e)t.vHЀNSk4-*kV"+)LEE-bU⑾5R&`ET֝0uYˆK0KamR *g>*^1[LcÀaClbFB^͉ND5, D !1Lc"z1qk5 )hF5fc8h=WA (mnP 0Bm~35nhc˷f7Wыƴ/fGSՠƲjg}x-A\쒧O)$TE 36|eG-0Y2_)Ffj(/貔Q UH#o{;]Dyp5 Lδ| Wl洃OG%kFm!K+Kǽc m1p1Kn&ٜ6y C5:K ӝ-X˲Rȴ7$Yma8LSE% \kYx7Q-WL, VszErUD+_jdaQvXNaB6;X="_r/ Q Ch֨+_ZmvF"!,jd-S,Ew`U9U)>9mV㼹ͿNI(7\Ү)Y h|ߒ6BqYB/pS4QyN$Pу`#H`ZN@FC4BpW=4C YH: $`+Ċ}ѐ- P# +hYXȞU" / %Ġ@AsݲXUHH=HmIi _O-M)!xLנȴ ظ <ȇbʃމa !%ېZ6IC1D 4ȂaaW6-b1\!"LR \\h!xa)܏^!3Bc4F1I N -|HH-#b%MN.'BHL\̌R8=-bK.cI1*0B 1R2U։94BdDJIP㯸H(#>j5Gy#+X@FJv>^ ?L$@^K0 AV#M$2U8`MdQDV$h#KdGdHJ-}OB%;:VnK LUJU NzJTXs5e,ee]e&卼%W+dIbZfaa"fb`ȐcW*(fe&+ bRfb2e%BB%;5#QiɵNGDĆ}wfoFmVmnfoogp pgnl*rnGxdgtJg[LE!Hq^'q&CԇPGV~hDjgyjVwn's]8{|BE|}}~~ep'u"wbExy"hzOl g~NhZhjhJΧDw2ꇂ:ƃg҆^(ڨ]hꨍ{@vz"i抋'h{hsFďRZNh*g Đ6DVxz>jopg|igziBhrƘ) ꠎF0p,B]5@"YYõ!O&#\jjy@B&Ϩb)@*fj j+kڪ&:BJ6+F.+fkj2+굆뭢jkÕ갆fCYDD.$*.t*j*l΄J(iTi.BlJĂ(BP)lȮº.lśʲl˺lZBlllϢmѦ})ǂJԲNF6DQ!xׂm؊ؒmْٚ-&ڲmۺۺmڭmߞmrftR".GʆJfhB^HE. J~F₮.n6^n)'n}N>FfD.Ȏ(ֶTn onnn2E(2'R/n.D~4o"'oo'o'0%0Co 3$/ Cooo&p#3p/;pKo&lcpko/p'oo #h ïR S0pqp [0#q_Ӱ o 0pf{ /qq#0q0q+qg00O{]j1Cװ1!ϯ!#2!+"1˱q qpkqkrQqư7q (r))r**r+r%r,,r-2,r..2./s0r%[11#s2r(1o4Cd'' ;76s7os7s!Gr93C14p&Ss;99C(8=7lq #9?9$2C:t:2A#42BtBCA'CtD+ESCD7GKFHWt20Hg4CJs4J[4KtGtJcKt"tM4KM4MtP4OPI4RuR5SoOoL?uS[TCoAw6|A6T5V;R[?2o?t8T'7s8s=dz7? ~G>gowg>[~֟+~#9IX-@ @~ ~#+3CK???lks'쓿Hx@ӿ@  4xaB  І_|"- MDh&*vɆ (9PFȑ22pM(AC5Z4@RK6ujTSVzkV[vlXclZkٶu.Ҹsֵ]{P kӖ+2k&P3f&14Ne -Sf( (b h@V͚h޾i%{wnݻyxfe6~/rˉ3wa'R$QDQFIr_4PBlh׷Qy׷~6/p. ӊ2OT8M=TUT2LwFY1Fuݕ^})L6Abdu]P%*J#-J2l =bO4CCEt]vd y/- M ䷨i!ڢB RhS!f?݌5ޘ㎗2b 9Iv.%Dg]$:8FbT)"h*6d<^馝FQǤ DZ㨮65jD:>>,dmۮN;Xo[_; |>ʳ5W~_oW?{'_~_?~o~W}׽/$ ρ ? FЂ49@ ~0<=Bp[@!e,$    #',+'35:> 9""-$$-**--.2-.;/+I11D87@B>BOAI[BMfCRkEUiJWdT]\Y^WV[RRUOOQHLO/HFFAHCQORQRQSPTPTQSRTSVTZUcU#F/41211111111238<@DPT^`hvenlbllmpprvvy|~zܸܢ߸H*\Ȱ!~"JHY?d'aQ#Ǐ CI2R\ɲ%KdwɜISde@z s#J@ JѣH*-cO>^*pꥪWg5/G<`]˶۷!R+ƧM˷ϥ.aLMǐ#K/ݑ; Zzj.^@"hbɚ-GRyRzK[:!lvadn{Co#|HAسkn,LI5iO0,޼|  şFxWLƂ5}o4Vhf XӁgVyPY`pD2XiɌܳTcUێ9gt m>>\tE'$=7dsMl̳ݕ 3aVX^A^CelgwM5eXe̩šY(ɭiѸͻ;n4m<Ӭ,?Inf3@:S1x:=6Y_)pQ FG R LHMRa0-CLa:S)vZpF3Sե4})LmPMMꆪ.UNũVծz` X*ԋլ'%ZCiTҭ?Y5@׺OӾ~5EK<b+X*cjXeˠvv@S75j}ZV}mleŭm= pK\ޚ֯Ea\ p70!Hrp]HRvv12^B!Kηͯ~mPLC'20vE{_V>``kxGL(N' ͍1\tc=!YO8/ziSъNoc9Yr}&;P/L[Vβ.{n NԁFhNs:ц,cYofܭ-xγ>m2,B9CXn t G a ^.-iICӔ4']pӏFuM} =հu#NAFָխs^q͆jD f;uhx^WFn{=\_ܵ6IɆD? .S R vOOKzv4oU;xHD"'NqSϸ7{ GN<tHWDx'r8Ϲw@/>sO|HOo ;ԥ.G BZ7uuNw=w5wG'O[ϼ72>֐E@қOUߺ;GXMBINv{L`v6}O|L@~{G˾7:jqwO~O?o8X~wc II؀ qo` spxcp0l (D$tE@ 283 4(@ 2X pP 7XA;؃@AH 6:1*1AaZ@.\b8d(`^ j k mmo @j nH| txr^lL۰MxԀFO8۠H 84x4XQ1SXWXDq+Axf)qsȆr z8 0 w Ǹ  8蠍#EciK(X瘎Ȅ츊ӊ)aXx؏\iHq( Hwv h{Xuĸ؈CC帎툎 1򨏼,ْ0(yt{y:錂 ' 0LyIhj"I!9Vy(TX](`4dY_y:XpR@)P@vy}9ߨ(BiB |d阍P W숕2xci])9Yy(+ɚ <ٌٓXJ < ۸?)~ri]4 s5|O>?>0u yyٝ P/A11-iٚI@ wi ~XsQ7p` 4@{p h#PP⩝#ZIh+-h4ZY8: w~wu`l:_\Ph'@z>` Н(\`٥_(ɢhY*!3y)ঠɦtX(wz~ p@0 x=gx0rwHu`]GL^6P% Uj# } c @9Q}j ްsڞ-r êudC0DJ#P0r0j Duthr@HF@K}\<2L` j } ΀ ` ʰHG ɯI,h j*jǚ.02;4[6{8:<۳4ˋC(Z9 ʭ* Zrp麮L`G ^\YIV:/p uk0 PIp ˰{ t ˝ ` 0:,a $khr @)SGq+>;[@+HFHJNoIqotgpR#@z#p s" ` ̰ p _}|P }` 9[ y `  @ 0 , *1kl`h < {?{A;E8ں՚j1{sM *"0)p9 uΐJ@z y@ kp {,m UW{@PPqPHo}t trpPru Z Q | \7U *!\qz.Ȍ}{ ,: x *Fº2~upÉb~`13,#`ly 0ѻp րR tK  kŘ``u[tq p{@*y- 5Y{< \ lȍ\o Kk8 ozwCJtpG I**/<2K@ 0/ɋB ٙ `Hp Ȓ{u`n`P r  jszPtp z܋ jpmCe~=Kɩw tDf Ѡ: q^]ko,OPיl ` @y{ { kՄ ٰq`   ) tbM֌gi|*{&]iP< BwǙlɾϳiȖvxuFY)9Hn YyKқ^+KPY&ߢ&K˹{?T "јu(@iqwȟ 73Qipuw@E_Ekj!N/QUZݖjHuY˨Ȉ3`o\~^Bm 7 Š3H oB/Ouzmoɯ() ِ2O`Id=$63 OhO `42o7 ?.JO@ CP~ 38D-^ĘQF=~RHBJJ-]lo9X7UxV.uv՛W6\5ݙҋ2U^Ś: : 9!VZWkV;X5tݺmnݷqͽK,ڽ˘4y$b?Y@-;|1IΝ=Z4/MFZjjW:!km`w ۖٶn W>O$fg"k-uȿ.# <2J)/ 2ўLE/lcm&bC ctn4ftͷ3N !s2*D4(HKGD.SR/SٚC&b 7UNEUSREj%[F[X=uU\]5U^cU`e'cE6t5euYiZk6[mYS7\q%\sE7]ue]w5wnO^{7_}_|y-SMg#$ada#)U7㌕1cCdOF9eWfeA>ygfo9gwg:hs>de(A:ifi:jX` .-/k!x反fm߆;n离nn;o7pGiܶx찹\V7s?}]F'tOG=uWgu_=vgW}pMAXD$dޤM1Ɠ5`Kr|6r˭Xr1OYп?|U|G?}M6NPԆMvY75Mx ֐<,yЋ1aa\uЃD8BPg;Gpi.bA]iugtMyb1k= i(`Rve'fO-<w`$:^6Bů"Kη~٫ؾ*z2JhҲ"jTha֖q\2,3F‘h2GqPQ/ < 9xJMbAUu$܈(Oyp_-WXo] 20RKcXo}s7Gt=lE6#q=hS,ә&!<``W| TL˥[2"XUU/T)U>e,XZu[Z׽ƕ=}Fvlf7φ%.NSJ0U6k1Ёu-SZݦ{)'u p\6!%o:.B(N)F1p%yE>rU4ljm{`8=u>t񷚣׶Vwvz^g:[4k=3ݭ0߄# QD3v5 ZFӉ:iD݋tDa8r n* ' 2AZ׳09åƺ'u_b'ϾSAZ{b" }P!<_@at@>poa=B+x8.!t렊,9S%: ;{`9zVˬ@Jh*Z8"s{=ڛ= < ~>=h6 "@<i:P6XA6?S"X lIA  7pۙ8%X6;My6?; @@9;k6Nx6I?I8;E;Fh5b@69D5BB;BA;s @(ˊ拍2`PAA @",?8ÙS8ә8h$y%B*?,BB ֣ Kh@E`60C=P;6Ap{;x9 :"24D@SĪF ?9c?wyOD'Dt\F?7x!$͔||ID:t7[;PM<، "pve̔QEt9H2A:@N9ph՜apNl>H%d8(70O9tp@P'x"!\Bę82|B8X:@7%ʛ+9J'zR 8q=@pK)d&WPFeL<9pQp´'P:pnlndgQ4>)=7XXC8L" Dj@vEcQMu s,ttDu|R'7՜ѬD-%.]A0S;8[&@A܄Nt22DE Y6@4@B=D\TRA%OTU>Xu=p%$BpA+E1,|0U$D;@ ՘X7eG:ȖMe TCeMHAxNPDS]͝dU|ڨU[噫ŭŃůř] 0E*/e_t[IFx!usc<϶IH6`9AdDS?@L?@>5]20NK=9>\\Z.EaS~@ S&} 9T7`YJ8$Va7&v\u^蕙>OEN`9`Z~1v8#lE;`B:^ZcEVa_u߬ߚ31Z`L%AbQ> Fv7X|u5Ӄ5U=XM<0<4Xe>pS>Xa;e@Pap]|lCzyE@F2lH7S6Sdl:zYŏS@R88M^vFN9_y&AEDXK qԊ]2;\>@h>X;P>2d8Gzݚ@_,J44P@dT K4G7P=4FL`6;GIX3XE-hiW&6CȥKiu.INi}eRiֽnꋞh_NPU@}7vm`7v[v776R>U#.R^쥞i_FlVWxSH2HkꜞiQ,^jg.k@k+nk)@k n pE锉lʶ镉 FVmq~m^e\mkqmvSmކ.Dni@PȗTo'7|8|T`wS~ipa+;Q'{n.W{wO4Eoq_0#7a8rW@&g&)*+GQ-F/0s0ShFr3%)G#697I:;m|ջZ>`ӯo<҉WzkFv^B Dn YBo]1ԐUMqlc!1x"!v!i^sUxv0ugݧQyXz|6x#Eyc~`Q8m:4"OLbEM8pxLiBm9o,AA tHsreGFhdhF3w$i9NiC?dB2z)TiMzړP¦ԣUy9 a 's0a]Y~YD+Yw<'l"O5:(Nh1dZ[m?)Qz-C~:njB)壨VjV%&"Y+1 ,t,c܂.56{0iHZܓ;j;{+Y1 mA nHm ȝѧ5>JoQMwqG?zǶI,6Yk5]{5`ge]gkN')6ow#LI(rJ*0 2v<2[%KDKttğu4EQ MG!q1Gx1}Cq,{T6><m DG:tw ѿ {9ұv#=w GV<$"E2|$$#)I>3IOİm32wCpz=G&!Yr"tUtꆙTuBknG!(EC~mч:tK:!hxCŒҩu|'<)y!VɕP Cz?m'(HUC%m 9$DD`>1p@Zb lYHAn9-N6i)ԡF=j:홑)ѧC!թJuʣa'<Nx* V>H%mr=lK[qяrdqzF01~&Z`@l?L=a@8l>5'>)ђ5Rvϖ 9{$9ܡ͈7qObURqVeTc0G7* BE*: [! -ȘkFy`.eNۘ-PO+җR]r#Ul|d0І6QzG=яuTb]^V =<!OSH!u;]kPC'TFӸ?r8?:79Nm0H%eHuL!Ƹ2^*iO{f9eN4׷ͥogWx@G9nC,!pִkcH-qE?ŅJP/$؋(+1LB8(F J4p(b}$2]S6bTm?!cpQ^^٣l2?3iG>1rLmWBw ԉ$z^7*yp#*iHG:>zԛ65UjWÚuGl]l66S؈Q85\~6Olj֮rFxpl1tFp}6X<8/cưq`E*L1^u_k̚%D &g++$H=rwG:ìzHO -?:y⣗{"P:sK]hiCO%ݍwLPFwu|[45#C]?.v9;vgq5]d»^rd7[<]h#:gïr<:1Bw~|TCobW>2SҮonYО)\5sChd-v%|ejD$Bm(v~6NjbhMR(P/(ƨh#̨hި(h)(L$_gF4N c;A&71e:冥-AD(rih?8d9?)./(BfeY"H+(LI)5,Ţf<*>9(C.6)Mj ou74C"͠Xx"ii&-.d.Di?')|J+&.+6kÜB&Uk `Zj{bj8z˦vD??@EQQfBfĝhz+BBnʄ"k,J+(,\ C/8µ<,FN,V^,Fv˥|x?rU)TѺJ(ت2Ȭ@<ʽ")jD'DL"+&. 8-6PB͵afDk@,A+*A*,DUB,,ؔA:DB+jĿVh.NIoe>Lm՚vnv-)})X7՘XA@C#P(܁_Q#ԁ.R+؉M*)Ž @DMV*RFL.dDX.Va@^+z%.%ʩ*M("A*ym(A"v:Vm?4W9cf#"|U$Cvpf/ىltoĈC/AoA΢"ʪ*̲.o2Qk7 H e)B#" B8BD+LVA$B} q01S K 7U催^.ͰɆ$L$DB$"$! f/t¹-2##(•y1(C"!rxg'f(<<0"O*!̃+}Ud%M)#*򹉂*l(31132'-272W2H/O35W5_36g6os576 5C$AXA  !"$"4G$q"._lAo/dzrS +$B?`t"tt+Z?B\p"-1J 4p?pɂAvD** $BB1dح"S Dgn$ +$`u:B"x1"l044ZᲴK5}Lؾj?#6d 1(B"+2 /8EDF'B/TvfBZ+'f{Y5j*亵k\1εְ]0?S^l_O'XDZ* aY'1cj/(tCtC@7++\*@w/xw6zFl_U ӶڶvNN p//6d3bJx78[7zG\۴ ,پ?D_k~W+As_ʀ8'8L7 ,Nj8B'੪8x8ZxJ,x~5|x;6wCji_;t9999;7y8 M6߰Vnio=::"dB#tGO:W_:go:wzKB 9cy繄u[hRyi7h F::6L˹xߴknt?;vy29v}G;wӴq753;@;ө{{_{lW8;G{w;'ֻS[A5WeM-#B#Hl5PB6ȏ<ɗɟ<ʧ<0Aʷ˿<Ǽ<׼<<|ĺn#8"0A+N7@O=\'%B'(B"|ٟٗ=ڧگ=۫ =ܓ=׽==>~0x'!gxOWB\>O~֛wׇ!=ᗾ>ۧ>>>~>3!;>䇂S~kg[=s)؋׽ }(>>WX> ?#y~kY>s=@1 2&TaC!6`D1f8aE9R0C$Q"eK,96ܼYgO?m%KJ:b1vX%")!AfUbE=^+@WZ hْ}nFaunߍ =kvl6D9*ISϜ?{ǝ:k|~y4)թ좎\Ԙ~vuk؄|N /no˛Bm#>inX0Xc#Luf湙̡I+]4O]WWs 8S. SN <:$A\5,|0Ç1ƒt,=ػI(4ՌF*LO ͒ "kq {O F$/0ଓOnE2+#Šcq4bd5zh/̓9(0lϺij:ABpЏLP!,3ǣKPS\/qEs*9-UVP"_+_cN;58&{C[Z1ak:}qf6J[̏ zx7(A |p$^4̃ܜ =o|f3.p%.!(Zi'n^"b&>(zx|x>z艸n><`& h* \0A&RmI]ڶ[Ⲗ%azIŹ}n sK|^ܷ$vX^p? r: 6҇ @At8G)p;r/ׅOsd\ HgLdFR34iD~GmJ/(%jN9C;@v4!U, {SI,ŖKR0/ g-$Y(D5CtDQ(:ҦYLQOըA UTK&]WOa~0YњVmuk hWu]Ε}_Xx5]ZW.ul`ľౕeNXA֫%PZբ4UiTHuUUkoVl+[G\.eLnq]N7̵r;n=u]u[^V}iW_J_-{RBd cN&ԌL.0F|`$UKS{B&҇Ԩ&a 8X7mP @ \c831&c=KE6򑑜d%/Mve)OUΌ<˘0rSq/2Мf5pLOpİ D}hAЅ6OC/e`!iIOZҎ1iM7zӝmiPcwEKmgZέ~xfY:Lu] 99vsu\ֶ6P q{ `vw)ӂ) ao{pr2$ w!Gqox+zb"w*n0XcV7Wxw rWӡ^pGUzձg]E'yi򠫼/ms=O>&O:to}8^'8\cga.s}lwy/^u ?pW7Jzç^g=;~eyWm7=v ؐ~68 `p?JG~ĥ_OA/h /n-L` pހX 0 ( TU!:6a!L0x&UM0f VWq| P.gvdcP0  /d p P 0P 0 ߾ Ő 0 P /`  Q0Mpq 0   qp.! %#VaJS!<;P ( 2-P@Y.o>/F݀ x R7qZE$om Qo:֯: | BD1!@! N"&2 rQ1(y|1O,V1Q~C`D/,'!%""r26r:RΑұ5:@x\d &-T' rtRj'(.)R2..C侮 oN*äF2$^`+f@~,@jrt2rb-./N/MP35Pͮs/*Ar*E*I2rߌa`r,a tRbhDhF!XV354^ޚ6?2B}4`ү2s+&CoFo'Q4G::@@4/o<=7S7s==ORCSr:C4%PDC` 3LTDR@%A9;RFF;ABr0o0ܬ${SފHl(T("tp4<-0kG'HRHw31ގޒlG4MtMٴMM4NtNNN4OMTG9G47Bݍ=7TJ@4Q!5R%uRU(R15S5uS9S=5R-SEuTIT75TM5UUuU5UYT46<-P*TLLEFXUX5T)5KU= BP34QQ44"u65\BbgUB0u/Y=EA1?ag 5)_ ` V;u V aPa66b)6cvc!c16d'b-_=ad?6e%Ydf3N\i-qdQV_MGqgy[A=cqsqUvT0V6=qu.:v@EO/lt6#6YV4W6mom/nppRAE/ PV]Z$Z}7q37.7suqաN_ߖREa=՗E9yb[u_L{j;:~~n̾ՓGsṯ8| j׈~wCϽs8{ :쁟1??߆;ZѼajo D5@ן2@K|u~|y"S ڄ0B~`,XX #2;vԘX&I4e@>b,) &"d0`gP6MFC''"z8㩏'@ZZD \-5رd˚=[,ڵlۺܹt֕+SୖyiDÈ+^11H"II%˾`i3A yh4i"*7ٴʮ;7ۺ{k߽BN(DD"Ndc%O7弘o.*h 0RMS<<1U4REVZU?XA&r5w]!4 6 ԡ@\ W"P!Oi6yZT8|9Gdv hZjdpHuq .gE=XEYdZt͖I5  J5TOnfIIj1V}n:(~ۮkn(d>`FSR)]Yi҂̴afJ \PhGm^n쯵l۟$21Gs oGn܄* J'Y:msͅ|yMDނ; "PpB'p*|U./ȠB)tA 9̳?fih*A+MtH t@K=5O;Ko]5_{d=Q}]Y;6n=wZͶpRx\M8݂mRM8 /829 dP* 3I'+<r”Z4)bjmL2i!x[2 |/|?}OO}_}o}. ؽb$%, A,0|.~Uv. ^s wt@.#ӤQЙHg; eHCƱ1@d% [%P+[hy8"((9*}\'S3U}0dY"ķ4 64· b^AЄWGDKؕ3q7k2EU|W9 vXD2]mV!sC&-q!8&Ky |1 x߄r]".|:D{G1~ 2\1)K4,|*8A*"enI\jf&!eXb.I) KK Q(,!lp% Ix hxD7j)@U2Pp*HA72ńW@3MRq,>&BSØVe824!|WP3@hM n(NG xhUB fi.q.Y(3gΕ&K<`+8,?\4MVBZ@@Fn0j֘.PŊTVRp!;Sx҃U^&*ո:61*$-(MJQ-mq֨kUX6gs9 iGW#Fձ a kô:iMb˜f,aD2 e4c*P 3ɠXW-oz _ⷾ5|^x p| 5]0{_?X/jZV?[U0)|_mqnL_Wrj9GNa Jw)uRZt \Y%Eq]`jk]D;ִҼ^%_m;>֊M+NǺ/X@&kK2Zó@(( W2WSn8ӺF]_Ѻ?LhH,c**wnbIBhB7a f'?HQHiR˦)A :M7*U"Z2Q'PV3XezF_WNra lfaxN b}>`M'1pTd@#Z%f4%G#9|>I/e٨6h\H*/D)$c.g2|2 o$5 4q[gcׅ4r||Fʫ3 w94{e_ ]A[3M#ox@~򔯼/k߼?_/Ooyң^~7;nNudRϮ`; /+oKԯo3ur@gI=!T:z~^_,WcU郈nW9VaFj!w^OwwQIrrxt7؀!GY[?1XluҀrw}/\\\'e1օ!h2U2V^0^73aOXa*_PbKaT_'Vb*_VJc&6c`WhViȆUa؅[Ȅe03_؆tvȇw~x8c3969cc@:7:Sdckp#fZw"׀'((+Hr-Xv!߷mljLG?6bZ^6i'(v8nwp  C) PĠKLɔ}tQW([ɕ]_ٕ'`I[Yeikɖmɕgq)sIW uyXy{P@DC<ᘎ FN}1@~YbY})`iɚ ~\"`)=9i1)M "]Wy"+1C#IIz 剞i b @Tܹ ٕ"@B[$ӕ9ٛYIzip K9 y?O*L-" syi_Y'|-Jw p9(`[SrB.>ʣ[韻 Y?yřDiLI&PWm@!(b^ڝc9.joyijj0M!JcI];W)xW9YIړK*M:F) ϩB VjZ]bZ"ܙ"J$ʦg yBӧ X9Y\y#~J iGJʓ3@cn;\< E Ҡh11#P"*&22>^ 4#ߊ_%:꺦2::슯J*g5zᚯK ʰ[) ˯;1 ধZ?ӧW)*(9@ 1˔c  NpX0o:@ds@0AʬT L)X:Z_.9-*_a+馴)\٪[y.۶ jʫL[Gʰ)YE27 |:u9e{ )[[lK]XT*룟\)iɹÔSJ l{9rY+뻱 ۚ+4)P&;*P䒻˟[it+zJ{ ٩'髾닾 ʾ +Kk{˿ ,4;[PyI٨ '06 !,#L%l')+-/ 1,3 |؋ګ܋L˦'p[`GIFlKO Q,SLUMlY[R] a,Qc6 \ܘ{)QyE elyZ{} ȃ ȂLNƅ (DtY`ɗəɖlɟ ʡ,ʣLʥɝlʩʫʢʭ ˱,ˡʳj1ɓ5qWɰ\˶LŬlɬ̞ɸ: Gx!MgD,,,lL\Ԝ̘G%ˀRϦ-g~9IF|-ѠLml -.w ǜ)*l/Eʕ\PCc0 @ bHk\`/]]ZQ=U ZTW\]Z}`md՘lRbmkXp-֝_-h-kv{uwtׅcmae-m،׊=Yؐ-َV47r=BF:#x<#-F.,۳]̵m۶,ˏL$ œm=˽mp,<== }rϱ 1&ͨ#iݒ#N`NP$߽m ஌6I4 Ҭ-M]D<,᛬ }]݆bE<+0P>n㗌9>!r]D+(.OL/yҩ9>6^~B+DwsA㴽l]xxԝ$$Udn1Q~oNY^>[C^3%Pކۜ>q~s[]_0?0D( ꛞ.ю~ܭ=dFOMPqR>^Mv 9ΘKWkn^ʌn>ޝ .Q-58\;>cA=ԕ * j'*@V+C d?V o?!7p"/$o!O+_).&//13#o*04?5/BEGIHOK_JSRL"pC ufxvZjRvg[pw\r\tUwfe]xu]y͵]UGFyl9 XxC8v&TqXT:qhI pᅗn¡f7@8ZֆR> .@-RfYU^elٚUfdg] z襕朇vzffinZ뛭iY .&lhݖۦ깏{k۷o~cCb5TN"w' n7.`9`ۄ<Ҍx`gfl٥v{vw߃xዯx㩽qC wѳNHSȼ\Y-x@{ >Ciɷ!6uO~t~qm4F:,GH՛(\dPjGQp`?u# M-),E#2WiRK&4zR9D\'AHbfW;e;HgFC#B,RYh޳{u>}}L99?x U?s:o鬤k8,Q/iEt=HoWR hjI}Mtk2IymiO}SUC%@T&UKejSTFUSjUzU QըFXӈ03<:-jL6f4GCsWU{ͫ+WV` {Xelckb"ø]yq eTV*`Da vիK2ELҢc[[mo}[54{k Vi1&BfLύn^M[_s6l1ʐx.SU,yUo{Jlc͋qi\LЕs[]^WWmvi `CXаElbK!q/f}pC8K8Z-;b.k8ƴ2{b"D+yOlb);Y&#׾|<^/uߌؘq:= s㜃A':q6}'xq*^ftFhH?Ҍ&_fδȴ[2X4 =1q4-:XZֳum}k\Z׻NĮ}k`[ظ}ld؊"Hu%iWڱNk UKI齵q=]]UՆDna/]o|Ŏ(m\[Ѧ#>J5-oS4ܭ+FݸBx7Fo#&bG+gy]r\3yi> \;ygs]C:Љ>><n1mqbz_ѧܧ_}W}FXϼ:v8u<$ψ~><@C@, :F?B:?@=뿌0M<\@k@T@A{ D՚8Bؚ-Z=|<IHP>P߻CZ8/ =@?ƣ:hA6AA7>8C‰BE47ƒZBrB>4D:X(B<`:p:BE0>1$4C>::[ClC:;;\EkEW4=CZP<"?{A<<0DDLEl<)|?ĉB@89c>DECC5F6 Gt;uȺHYs?uB0K8X 37 2 [1q|T<@ g䃚 ?@);?ODȘ>/I <?ɝd<@1IcP;N>0a MLHO5;;pnj>ζλcG( I#4 ){BMG|IF8y? DШ@ۄ>NM2WDE@w=M@®cHOA};C QM@HOǦc'z=%͊ m\];QVHK-<@ `;Ӻ:&mR#;RQ+,;,})>&#D$ !DHS5]S6USI`OhS9=5KSS=S>S?S@ TATB-TC=TDMTE]TFmTG}TH]/=0e=R4S%M«3P UQUR-US=UTMUU]UVmUW}UXUYUZU[UZEQTQo+ Tj5P6dMVe]VfmVg}VhViVjVkVlVmVnVo!d#z3%Vj5u%/vmWzWxTxmJ-],^Qs=ٲyE,y Xm/M/{q5BAr /tKX۲ZX؍UymXK%LtXXMٕז-֏UYrXsWUeٝ]מٟ-:3-Z/] ;DI 3,{C1-2:/.Z, [1/J+C21Uۯ۶2:C[ɸ[۟[-ۿ=[\[u۽MZ=Z5+S\14̜3d,$ֿ %H7PW Zz]u٥-.Uy,ST 6P7hڭ]R^ƺ]ܝU 5ݽƒ!,$M]5]:X6${,G]788߂9=H9 Dă%D;H]%m]G;_J_Z_"ޑݒœիe&X?`4d"";xZ04:6:96`#vA:p:6@C'>^;pFbbaqm|}ߜ=@f}Ӊp4_gv~gxrvgy~gA0T)3ne4C \c:a>'ቨ`&<(`"fffff7`;6k/ْٓٔޫ{;W]G}}~28hvfE"@f6xCx~hZ(cheh@xjGdi^ٯ.kFk|ic&cfe( 7F;E6A<8?a4ăB(4cSZNDcbȓ kZl2EEeg_:W׶ U^Z1^^^iRN>&㦈.ݛRHknq`Z\63nޫ䞈Ce_ŧ%Iunu\=>\!Z>m[M=poƝO\n2 pf0 pWԌM;sM3] %]aE&~-HKZ\~}9,Urg8oZq0>e!m"/]#7$b'. (oW(r ^kUn"W0W7'81w-/AH첶sr=_sW>r?tu!k42on| huWWPuZuJO0u^E^u`vb/vc?vd?vP(fovg/AxvivjvkvlvmhwbHvpwqwr?vW(Zm_wvgR wGnowzvb/b/]/v^(ZwZu/x!h V/xu[ucGbutx'vIgEh'eVG>w^koy/sGg P A8h-Q_(v|'v}7~oxO 8xHxou[ecxkz3afoȴbcxC0z[uUp{UuQPQ8Z{7TSZ'|?|O|W9MSBHSHpA`?:3au?W{/wW|oFh|6AqI@[_g|WP|wb/zG'x_ O7eWXWyc8Xx'fXRh^ۄ[(O}PPQS8Qu/Su e*"LpBB!!Q)3јk\I:T%TǰY۸Y3,52r&͚6o̹ѕN 1 *4"E><$I*I Ji(CHB6ɫ@>MvmZ!eޖvn! ~Jm]4nahzcW&EBHQ(/kpj(=D:UPҮT!<5 )RB;h - ޼'&4ꏲIP$!bc1alg7yE|hiӛẍ́*QkuJ*RdHї}!o1'rK1sK)RJxb}:uR"t$H4#~ӨB:(4 /fyz1@iA[%~ZqUW5% u ]C!GǾ!MҬ*BЂcJ2ܢub!/ RH#c"lt7BG"7{no 'GLeBvh qxxqё1l% <+0; mr͢,=\)NBGqx (G1i "d2JBorH]RteJ"gn8Qz\f!)(褦s*5e񆧽QH*}'W\ B`Qk5tX38 3GJ1lBq8`A(!#DIbiL:vxCQF+ E2̡Hlb%, [ZI(-DՐ/0$3o4o(ٲ>!h46A'w?uX\ )P -\o@5aT4A+l!֛Ċ&Is{k齃#dȃ"04aF &AD3:ęO#3F\dBH RS`@<P2V=nd c )>vnEF+}f:P$"N; lHQ#$ DwJHA;hTI#Gġw( &JCmx8DJB L Lʛ(fA8nAedM qp)?nfxF%l ĽCjR6 9)Q8 MD47C bb N4Pcԃ q=|%8YN4w ѩQ旮+*xQ`>x`aApY(yMVgp#\!&Ge$T X-1E Q.; ,?uQDZڈ)x"P@ZJnvњ$}RTk4LYVAIP%,tcv(e`)RYRŷ\6klX:CY əCA(I%Wj!r`Fʼ7.q +:BXg:tgr@\޿{cFvRޫRMo{ZGR.QFZ=zτhD1L?A"JQp%~)d$ Qh(F;Ŷ%lgGFbc |gicPiYJRMCQ%6F5n/͠$f6Qp&%qSDS4uQcM4*t rTe:BO4A lj0W6-!xl/G¹y/‰ m`<)#4Q*r[*XWC1Dî8)L!B叡aT فiƕ^4qgʖNi !(@lRR&H r!!ta!aBQF*܂)"pH#t!( ء~B#laa" B^a!L!lmM!!&Q P (BN'b 8b8(b+V H MB$$-!)!|B'b)!-B!pa*ء23҂)!a\"B) (-(#-c!7!-H-'ء$bB!b?!8 b!8B.!c l")!cD¢,Ң-b. ނa*? ,$ @KK:!J!+^a5-#  cR*R+^a#2bDJ+zTb UDRd,"FbO $RZe[+%>V%@ޢ DGA$%CS+z% `Xj$GzddDdIdJ:!K$LKFa+b!OO$*o@FXejaSrf`&&6kʦ+r-&b2f25G)jo%\%kΦ+ڥmڦA"$[e?k&r.fndv16HIJRfZ&\dfdkdOBg!pg['(>gqg}ҥ B~~'nZ [Ƨ}R!' e_fkΦ ZbjgHczd$VxAy2aMn&Nbgbzbc{\Bh5B&d$i(|ʨb!"i"_2烢lhVg^gfGj+rhd'Mg$)*B&X✜iiAh {)[i"jje*jbvvnhwzd)Z@  ڹ2KtnK7/tC8P$j($BjkrK*nj :(AB/Xk/B(B^kvb+kF~+zk+++6B+"6*+-,l+n!\.*5ko«ZlګƚfVC2l1Şlnvkr B't jHO*Z܈bfjrbgiORV*jozRv^if)cni+vwrxi6-zZN׾h޾%m^ؚٲbzhxz&y虦ghfj.rz^mraٖ!r퇲mҒiܢh~N݊n\ jn!ꆡ-6n$*!Ӟg"4no[orzg"c*2nJ>-Ԋ/֒o[/^"oK®emmԆo .is"$ ' #( 0%( 3,,, q#q!(q;C3qSq[qOko!Pq1p!ϰ б 1 0 ' 00 ,("3r#;#Cr$K$S"B%cr&k&s$_r'r(2(()"2r++2,2"?C!q 1# 3 ǰ 0!r3;3Cs4K34[5cs6Cs5ks7{7?3787¿s:3:2r.ױ/0Å 3';03@8A#45'B'A3C9DO4Adz<.1>>1?/CtJ;CtK[3KtLLt4K4EX4-gA #sR#@WBStxJ#x?Uc5Eks {uP1HH1I28J3A/x74OӴ?:G-Fo4=G_Xtu@s 99yx:8u¤Sz[czkszw)xzzo:z:zS)uz˺w5[x7?:{8+8C;{6+JO8G/ 81#{G7C88C:E.V&;;-.N/ >k>S>sj>N~Go~>+룾竾~~lmVҲd:=[-_-W?0oӴ\X71Q.EiGNMERekciۏR1oMV%-[%GgzcvxސC8p#&TWTkNRN&߃́6}Ia!F_#.b7NdI8OnY~]X1~)Vyfmf6EPtVM> ǐNڐH9gUUgiq1N}y"^7ް7x奷]xV& \eg.g6Z$E2kdu7Mn epAs\ $&y׽xxemuߝ}wb؟u"py v x b aA{Ǿ{衇yBg&{I2%WP@ T@>@)XA ^`9AVp#`BH2ajG"(0 mhC|7BhX@LaS0@XC#ްF A>ΕOu[J$&- &XF3iT0q(7y#x% {ḏTXŜK]Pڟ>E&&9IO~(IRT%*MJWle,iYKZҖ|%/cF=)PvE\1Le2әѴ%4YWRӚb} I`fQ}LJʰMsT:Nws |?ZP}]&8'IɕԠh:!:QNը@3Q(Bo&#Y1LiZSԥ7NyӝԧAPo TG5jR:T(i6YUUrիaM*XZVլ]}*T(P5'ephZS2k^нNzŅU+}\ X>d[X^,K-Y~ hIΖM[%Ŧ}ie[ֶ,nu[ַMl[7]׼%xUvwkݘK/6׹,ȉt\>%ͮxzwy{^V7ۅu+7/~wッ}^ Uv<Kka3p{aw'/)n\K7lw=r);XFd'?J\e+Ye/yqK󘳌dXdV3Ӽf7so?Žn%=9Wot׼iN3ҡf? fR˧FWMhUZ5 !U,&   "$&*28;= !<%%3++//-1.+7.+E00K74KG9J];Hg>KnBMlJTfZ]cbd\\`MV[;ZVcRhQiSfUbU[U[U[UZVZVTa Tm `x X,=12122222++12121212222233D=IDkO|VP}a|wntuvwwwxxyzz{}}}~~~dYp͜ðИ֓۝ۯH*\Ȱaoy"JH7W#aQ#Ǐ CI*R\ɲ%KWobʜ9UC܈ҥϟ@ JѣH)Ol"ujUG=#P1f ²Ӫ]˶۷pʝKݻx˷_*"X0cx)"Ƃ 3>x *CV!0M^R?Jͺְc˞M`zͻ޳xN*~-9ܿKNسkn]9lY*|THVQoA5Kѱ9Ujo8DP !V .DHV8avaU|.$6XE$(v&0XE1I^ȡ@,)>XH&L6 nc=wUW%@vT"͘I"h.r }IX[P qVPdUQ'qf幧dzf}.z4裐z qGT)駠 *Z"PZ'o"(*aZJjbk,{6F۪Z-]S)${dVh$j'B:CA/to , /|p COlg|񩨶jTLr7lw*2 1l8+1 ymmAUqzU !@U`u.n[wg׹ ,Rلi}vj]@o 4w w]s7~x7᥼qW>|w~¹چ"㇋az馏.⣷^:꧷zn۟\ߒY3T!AYcنTkkFO@v}ׅk& B蛏>I?@??ǿ.XL 7Tݠ /O}'@\a`=a8 GA0 gH8׷Aʏ.H8F s@"P+¥<#Ρ bc@qP^`Ŭ(n1@f<#HYUaVoUF7tcA8 /U BҐp"HFQic$g[ᐗ3YHMrҒd =ML*WV< GYE{T E& 0ILbVLf0[Ќ43q d5 aӛ$ɼ Nq38YNnf <LMwd!*?=- jЅ D'JъZ?5:\zt!. #Lf*%EJiRdB0f&ja2@36Mg6Iu36cy1αwxb* A7zR so+2:A)T0_/ pHEd"MxH9xγg9"BЅN"`:isߙMIOҘδ7NOstd@{Tհ>A`j9cWzֵi}]v-Af$0t}iSζl@u41`}mv㎷Mzηm u#6t.h&S>v}ker}lo!^dc|9184&?W,Av8Nr|42ЇNHO"v^7}4`>`1Xַup]1h[ WY +p Ȯ]-|OxO^!_'򄧼7{}5x HWֻno\T!8թAO;ЏO[Ͼ{?A -O2 WAoOϿO؉3L8@2Ї ؀'GpaE"%|'} |!$X&x(̇o(Xd(Ё*8}#|588:oqX8h`:z|YIYh}hȈ8u(iʷXYXXƨ(@Ǘ(}(tni`sǧˆ}P'G w' ;xX;8yH|؏7i8hn`h|yJ_(]Ga@|v@l|ȎtpI}!y. "x} a.܇'ȏ'Atw Y|䘌 4[԰k|'%9}'I|M9ג0`)}4I|c~h9)Ȓ:Ί}Ê.x)pLj #*ZgڮCjۚݪiﺯ *Xc`N^0Ʉگ ۰*efeY~k~jz|j{(}{'C)#ˆ4[ηҲ1^k6Bk|8k5CU2 C5[3tL8@FP۵+ gp9रjl۶m[n;t[v{xz|۷~;[{`ʭY;yq+{u[;A۸aR{[Kik˻ʺ*vqȋ l%F;Z[ rѽ[zrkz+%; ު+9; l< Nߢy YB([Ux .j G0=#`G0C0!LFMf2*l {¸{R,1|3 <|rRMʦ?tݣ2|-|}T~Y=}`qk%.߿]ؕ*.غ۠0 =ЭGE~ۍMJEn >] [+-m9Y-i~|MQruN$n aߔmt~|!u&T~ؐ>^wN4N}\t-cai3f^!n 4Dn.2մ;~;@N^뚽NN=}t=K+=jnnd4李 6^U>߷~؛{>^Ȯ.n ~C.䚎~A[>#;ݭeg] N,_㊍Ď.'^;^FP^n *>>Mv^jq+䡍侞۠-^FAoCoOv+C לSLѬ?8eH^?-5/ˣ;lܳ[ԘH ] il m \U3;QKp-ͦo/Ƿ['ݺg ?pOŕ ˉ^]Q/nٟ_jo{?K@ DPB <Qć*XĘ"E(P8ba#j%MDRJ-]VQHL5męSN=}TPEETR5rHQ*E/jH$]~V,Le͞EVZmݾW\n=:UoE7vrd]Ȇ=\X1ˈ%>Xdʕ-_nya^T^UpWl=]DÝ][lٚrv߬&Jq۲r͝wu1 2?_Y1xӍ?&^zNO[G=apó? @T˽h7b(<o=TAd02G$D/7!L!/(;;DFA w䑽 Jѵ#| /4(Clǔp"'Ҹ ݼ! ɂLIǪt,ʽdʮ$(KvAp#d 2]j!PAT13 cS5dѹHvB3Nd@Q2PKRCSQeʑRn4\ETNEJUrXc5DYiρӓJWDJtXے Hҽ 7Nut} ]R Zjw㝗]w_K ?!dH,LFѤO8~8+86\&!z>F)|z5ߒ:kk;l&lF;mfm:g6avb)@-W2dijN)TI:]r* _ f&nyoAW |^7ٰ{uXw@S|*iu/\KBgozyzνwWojxx}OY zS2zˡE>P2+(D'\Q[p'7 $K;Ѐ'DaigN{C&VQ"qoTЅ.raZD&*1 f*FhKJIa~kn; `CMhbHb.jQc'>Fbx!&ZbԲ-Ҁt e@fo Vao5Q;q}AʐCH䅎`⸉fBĤ(NꑏcKFRN* VFcOl▗d&wiG<Rexm!Ӝ2f]YW.xX 7^3d7? P Sż92.$H;FhFp(M]ne0EseIDC)2aph>=~nDmI@CSTd3YV BRK3&EyA>I<*J*FER0~>5J8ŧj\ٴU~B䙣', 3,U g(7ZWs%y@lbX6ֱ],XpZʼn@ֲlf5Yvֳm\gC׮P,\ 2&a'2U)ŭA YJdo u0D<) % `<شMmF6]6p +U/{+xڧvc\( e,c W!.o5D~xY_:U&)[[W0ÓbxoqqJp-3sj0U;L;|\ Ot#/l՚d/"M~rLfp^ r.qqp1/zsgF3e,<9t"(D"JdEQFU-ϯ>';%ю5ukB:0| #ot$>kSxz37_y\65Pmmkڎkc^16}U݌i"k2m=W!STCo6ŽqK;ݸFwanv4髴/=HT0[!@yD$]4mnw^xQ8zKi?(Pơ`)σA$LЏB$JD(Σo)'Վm>tp׽392ix?vK2{6́YPՀƲ wInmwwޕG{\]ȁ/t{bujڒ't}p] |=з;s>O{tn S:T}l^ߗ8+ 66-g^^qS??~.7c?3-뾹˿T4㿤s77K\k?20+0 @?b$K[>)4@,CP1+­[ \AA8;7&dt()* !4T L2Ī|"!C[38)4>5<C72>D&:t6;/42=TBD(@|!6D73ESbC?$DՋDJ,DAB7x704PUDV<3XCPiZ[\E[]_`a$b4cDdTedftgD p203hmDUTET4**h(Gr$`uCddx{z}G}lFH\y XuTH8Gt_ p`ޣ%UFI6 > FP`*ᇐ!~.^Ya|bWׅaaa "#a6UN&Zb\ܔa:+"b00c$.cVEu`66U9-c .c%@n5d$dDޘx Fc./$6cQeVFNed ee-0c@*h1(*W42KvmHegZ)^d1 2b>fmD1HS>b2vfyu6uXy^j>lgM~c[c^&ufuvTcfmՆԆF|M~Nlgm_nbn^6b/⃮UYXyh_hg]eGRFxxjYeYk j_|ih5Nꓬ選i~wVe?dPfgW{&].˥&ɦh=dx6i6]XjH]ى$4kX@V&ij\EuWԡV*5ݼF˽^ɱƿi6e.ža@f_\GҾ[lENfkTl%46^ЙfjeevjhEVfvVܾf^kNT %nh鷦EMh.ke.>o\Wk`fsFfeffo~kogq&gGgun&lNdmAnWjpm^ho瞍oolnKZԆcnnTY/rihj&qp /n!?n\L#GnDMٕ`<t,\5Otd'ghgxEǶkvLY$F<=ntPSTTe2?rWYXOs^t[qGr~H9qV7uZ?Nv\shj}g?K/d`MNWucor/v%rfMlsweUt_wZv9;pOg7d^fFii6Ftx=2wlO q.gca6n[ZX/FWw}x]vxxrnwFweUUĮkhwigڙ7zAx.'Gs*rm/vuo8j&wsRxFJ_qyd{!sqozW{7ze|w|zwpOO?tsV?{?b/x{z^ן|kJgsEg/|'rG|:Ծ|uwOzMy|vCWx_os~$/%7@xbWp3)_ j D F2lPaƐ)D0gΠA3*"G 92H0e# ʤf&Μ:w@B(ҤJ4)ԨOJj*NX֤F—/G(X*N`dI(EdR,Ø]"ܺUT~)0bć3޺1Ͻ]uH,Zjٖ| \+[V^&߃x5쥯c&:6aK4gӮ-ܶrS[ ~:vĭfËMqo ϼx[$A3 :Bo?aMU;W d"Ș'oGg ɧ\uV~'`2`+X"aejV\]W7ه6Q7dvCH K(ۋUSRXMF#^6c|]>rdW!Z&NIM*RV^ݝ剛Vxbyb\~Dh$m]FzIҶeݖWp7V%shĦ^j)&iU &lWz=*+ z$gsJlcrfe,hLibfHi򊥸*-o fg".*S"nk#@{Ut4FaF^ G05|PSDQdQlrj2M;4QK=5U[}5Yk5]{5a=6eSǖLa1k9lݮoyhm3k q|}x68~' wIWz?u.z7fl#6,91 {ΦzH0,J|6FߓR.r­7<޶.>Z:Ozૻ4y:U/-}7,.@ /{^=i!q4BpA Q7@ &SM<8B0' q?R{g8&/E;_ D.17aRu.Tڼv#KTw,Y$<e#ĬU.NdStG(gE2"4 ~U#G)v|d/dgbhDNIY0 2%8ɝ1x>||h+NrDZU@$*9I5`d}ؙnRutr$%)́!!Y̆0+y-]/;L8B;f8AZn&rL瑦Iv~01X.2NЕg&v2Z@EYMwf%4'CyPjO-(тv +jI"UDi-cŝ_|i dޔͦԥ2N}*T*թj WhqT)AP>;V-C0"Q~L+8*΢v5 nQud^-Hl,sNrIy.ď? F8BH*uE_W*VͮVSFՒFF++˲\(l[9֔a|5WTY񖰃iiC{}#pڰ kVwL[=.,9;Zv}x8{**rq[ ݂й,omM [@2إ`HH߼'1\p+80[R;.Q n=+(Aca [07,KŔh@!_aMbǘ50\p| 3m1RK!^zB,3of W<`*py\Ec8e6m5Syn)D/ԙu ϘVLt)8V6-zFaXZƘbH` g`VC7ȋ\M6XѥzXp/ aId$Rf{:ȥo{JMj5o۞uk!^&ߞҴh iۙ !M&=ksӍst&yw غ69s<?y֗pkNճ}GO^ UoUI{(3_.FAڍs_, &ڏ}?ϯ` l{]?b=эEFN%P-5` #Q~ Z q&iP?  `5ʛ\i n Wz%-eK a``\ a !V5p _: !:Ɵ͟z!ޝ!,a Km! z!H ."#V =!b6&n"}_D4h iU! *"++",Ƣ|,֢-".."//"00#11#ic0"4N#5V5^#6f6n#4&cq ?53rc-J#9#::#;/z# JmM3*=>#??"DN$EVEb@abC6*>C*~d4^$JJ?f$n$35dHHҤIcD$OO$1$%MM&%HNS>%TFe^rRM*dNbSJ%XX#U AB:dC2Z"d[vW\%]cYVL/I֥_%`N]_Q"^RWb.&c`G>HlXj`fgib汹iȃ)d*&.*6>*FN*V^*fjg"*Fhn***r*>Y*檮*aEh:Vf.+6>+&mbn)F+v~+LNެg+*NW鸾+恾++,,&.,6>,Fj⫢n,vljlJذ~'^jʦʮ*˾,ʄƚ ֬,,,,5''AAƁ>RV^-fn-vz-f!58-BA̬׾-ƭ-KmȖ+)-* ,. :jDB-V^.-ީ2Vj R6jvlBAf.֮.*Қ+gk 릮@.뺭JnFN/nkn2oMn/lR-oƯ/Vm,rŒm/wnoN0Wos/akM|/j&0O/B8o[ 0 -($$+t20B>o /17ߒKT Vi~?/ oOm+-v(f񤑱/ 1B1"1!71kz7b j$ 2&gr 'no-LUrL2%k*r'_ѱJmrۺ/+2/G+Ӧʪ, .)*23?3r2dBg37w7'6/_#7ˬĮ3;;3<dz<3=lؚs1>3?*r;r>A4B3@~&DO4EBsC7,UG4HES ;4>KkHJ4'WDBM4N7KO'~V>tmNQ5 &A4- TOuKw2,tPΤt(kTS5XZUrV7Vʳ~X[#N5V4Z3W'MH^+I_^5[avװ0FP`G5[v^?Qds`Sdf#gu\Ur$6j_Oe+Wg6e6_lKK~6nn6oo6pp7qq7r'r/7s7qs]t5kW7g]kOvgMIhh361't_7_kvwvw[z6xwH7YYucdR6{?QaCjW 6k7{4~\Fi;jl78gt7y$k8x7b7Sx>80bQc0Mת8Ost7ox'8w24#cWO93}RstS1Fy9cx t8*F?9Sy9vKqp+vF @8z7 Txt @ D:KOS3:lJŧz:?:\z7z_:x4l85WjKK{ ;CC:{0:k7{z5;,L{Qվûw{_{W: {{c]߂%%܂37+do|+;^( 'C}G>={Aʫ=A= >Bp+qz~:[>Ǔ}|_wz 0τAۚh͋w:-,@ ̂@,j?/+1/wpe{> Dw0⚾~~l;?@xq5p`#Xy%TpB .4bEYx# ?VQ$I"EȑE".sfDKʳs,j,phLG"SNFu iUV/B*k֭_6",Y "tx(УEƥ ]H%7WX,o>`W>xCf$H%SrcK0)6 HA3Nj?)Kn2ߐ.EL/C+H 1=(?* 1 /[TiT"DB5d'AQOܯFEm@ҒFmQGT:H%s;'+<LPE+zTF# շ(QMuUU[̣jL$4SBsL MmI'|rN'ԳDY<?}1ѩUK2!r!}H!dW!/=oQZKH@cM.̈́N(׭vݨL`AAO;֘nY$YCYKIISѸ,1mu *k(L7M9^ɝ՗ޘ"怟hS>d-UL\IJMm5 LbZۨ=m䥒˻\^El&۴噽=X+KG~~MZpцjFP` Jkvd}ϞX1Y]ѥE0f9fsgyC'F'\,[wK]lL. K|,| FEP>Q @2Ё`!= . }ѿĕߝ/c`4 hR`aBBhB! q ׇB|}9T\Xpvn}IDF&QS44(׋ 1GGVّ;]E⯋!I;Vtq!!ё4#$HISI,|,XyA/Uȝj* c(Ap3 ^(Ƹ2Z/La41Ket&Lis\40 e+cyV዆#"qKrt;OyΓ%dPaΩ;1.r*AP.' 2-r^<@CFRGARs E$ZSkMd"6r5Mq>4~ O4%AuT.5iLNZKՉT6]WTOZTiETZ[WFV$OԪU_ Ѐ3hAVֹ.j]%̀%l Y5laVN5iQZծuka[Ζmq[oWǨRd,f;56aQNe5SXMr\BWyћ^b/w; >7z_:v^&~``qo[,$Up!lp%6^Ānxal5CJ o}c*1E2T% xAGld)O*ʃ/d.tf1[rw\'Ql2OlY/W`hAЅ6M_Y.û Ǚҕ.paQ4d]-jQ;ӚpZ)΁yԭv\1=Uj-*_k]35ejjzһ6skNtC*liO;'ʵimRۆKnuSܿn6T9Npʒ%םo}{&79oq#ꪋo/܁+jg#< ŗplRh1rէ>+%nkD-wasϜ5qs=ρvSN.MwӡuOꨒ5MT$]vܝ[l[Wzwϝu(UvRx7>Yc!yOUzqA|| sw.י^W{ aH{wo}|/ދ_{R, Z޲M/fCJKꓓ<:?~ϟOgg^0H/fFA%p)-105p!=A0A0EM=Et[[d@$u'<$Vkp#~E6hkXb40 P0ln@P Ȱ 0 p ٰ p pPpP0X@x } 0 NJOvu %1EqIMQ1U `]a1aem]qqyu1p91pvگRJol?qq1q11q=11ٱq1ɑM Δq2 r 2!!!!!2")"-R!'"5r#52#9#+gr$ P$=$]%%ar&aR&i&9&qr'+R'y%1)jA4* \'()2*r****r++2+2,r,,,,2-R,GR%O S2l*2/*r///-s0 s, 0.J(OR))A./21335s393=3A34Es4I4M4Q35Us5Y5.1.1(2a6us7y7}7387r88398999s:9:392.23i3)s<7vS2r@^i Ġ?I" €?)AtAAATBKBB#@яBD9tC5B'BGBKCTDWC_EOCcEEtFIFQGUF3TG;GCEktGHtC4EYCH[Ii4IMHTJJsJJTK{4AĀ$X 4i1ߏ;]M4NtNNN4OG<Ӑ֔3(O P P5QuQPSQ!5R%uRQ)R15SR5S=5S9SEuR#3Ps3TOUuUYU]Ua5VeU3U5kuVq5WuuWyW}5OM5WXX5YuXTSUY5ZuZVYu[[[5\5\r\\5]ǵ\յ]]^u^k^4!,vH{22nZnZZyѕnnZZnyLj˴谠ǡ8>K# :89C̏ :ѰZ $˴KLWز,,8y((4BCM33?CDNLjyny..:ڹڡyڡ ablH*\ȰÇ#J0ŋ3jȱǏ C鑢ɓ(S\R!ɗ0cʜ͛8s|Hϟ@KJшA*]JӧP2Y  ȬASiFK,EM* X rz &^<ÈU+@<*H baW }5^mcƑ_sLh&{o@Cۡ (p'/\fʺyÖA+`hXKbٖegv3h@GyFFFYna v1@ rYt݆e8EkF8_iUE_\`Nyl(h4fR:vA?m%}Ii*sVsFB%W|v T\hd'U`7pg 0io0(|tZDDYYz*8by*kHֺڬꚭ믳*C `K4+|B,f jAknT`l"pt@4k/l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7xD.WngyG砇zn馓P9GN ι஻3o@ $@CKy <T ȃ_^{c <̛ӷn>׫> ȇo67@q$H=ISA=0~#W=O;@%r4!P>ԭc @Ztl7!CfЇ2]( HXWyX A !.z18 H-0X`!Ht8 @J :B !,}22BCM#$8>K KLW㲳,,8((433?CDN..:  89CablH*\8@#JHŋ2ȱcGCңɓ'A\rF0a`Ba SK "yQΣ ) ъ "=4LAi^ŪiL]_@_!T6@Zb|S՚`fk+ּ N${Yp&&aUJ|Zge~㹁 W#f,6gDAؓcĨ,;+:`ʜxB[1i_[wu,|ݭ7yJH=uOG5irnג} ᗟq(A'RnDBؔ V8у MhᇞU&$f%0 48xc#r`:I0mB&$<K&I$GXeT"d%0ehdp)M2ЀTB#h@He&0(l鞌Vj)4$ L@\*.:00Aj@!,+22BCM$٩ #8>KKLW㲳,,8((433?CDN..:Ҫ89C ablH*\p #JHŋ3FlȱǏjI$I(L)Ҥ˗&I6qk59&ņIh)ni:ISuyd*I Jha"*)M[&0bꨞN3%Tw@j6<3J zzJQ0\첫:0+ L!,c22BCM$̩8>K #KLW㲳,,8 ((433?CDN..:Ҫ89C abl#@# " !AX!ƌ9vD$A# ze Ȝ0̓p9σp"i!DE`ҞGkÖ?(Y[nm-:ۓ) r(  ;,elZl1m!,+22BCM$#8>K KLW㲳,,8((433?CDN..:Ҫ  89CablH*\p #JHŋ3FlȱǏjI$I(L)Ҥ˗&I6qk59&ņ^vnžwS독CD!rƗβMUWuY"<& w_q%?t~UL6D],A! *U(сN!LB,b04A<@ 5@)j@F6.)dPd,Y\)_v9ӕ)zhYSkR&):@tfjLgn8޹ 0`<UZy@vj8@i\ @ꨧ0rILp@pAҸLzPa Os"T  6|u'P~}`L%]癷q#Fu  bR/Xقh#QݎMv@( cbGd>w#E"5y!8CsHq ) b` O&u]#)9Pwigg}EfG' 9CQ  Xy-b(b 9VjD9f.B& ݹ:x!Lԣ ]륙dw|$ '*꟪ U(jIyik^fvfqJaN(!Bˮw^,De-X|#HGZ8 fS"WB6CeƆ Gp$$q~h1TӅ^q46r$drQs3li7㜟7*>C}6W1mAZRerU[V4lpݑ[1eDڱ߀tFdxG*⌛P\kАxgyX)x D a樧zME0C ! .Jа.>8$@>'9怃W8_ac *Ow+ #@1F篫P - L m`T$фHS'h92!(bd DL "z-/ͰHzH$kk ) BBb - 6x3!:+h`Eb)qQc!DH#FƑ`W#/e|{4‹І: (EOyNEO, H-QJdRHڀԥxK9:<'׶FӞ&J/HŀTJĦ`Y(ӪZ5m% Cc X*V:5"PTҫ$VV3}f ^W"iT'~^dkHZN'RX_e=<"g'ձjVJQ.y=g;z6 mwKZ5}mծ.9df\¢7jit&m-ea ^]n @ּ7]iޑbW])w"O Io@+J6ɩ|Kw(<+` o[>Z -hXaK,zv&̬!᱄x0(aζayqw7+ɌAιF$HB&;PL*[Xβ.{˺dBX,H6po@:xγ9π# ~ K+rѐ4,J[zз7BZ'3XMHԨs9@SհγDwqf6uwWt5c=Ԛ"& kk^;[v-mN'ǞHy6Mq{ھ)4}RLfj= $aܶs-oz\7> ugx1moPYx5;uE v@ЇNHOҗ;PԧN[ Ǻk꒻{( -rz/ dEgEiH>]y;msul㄄ ET"pOkSxDE`#OCnѼ+n:}C⪼5yGGl4Xbg?vN;Z׈ -gͻ l|go?ذw=n}{H__~k+O;k~ܦIׇ{'vGfzoz}7Z0^!j y׀բAJ4}\Wu.0284X6xKyGr&s9AxjXCHE@fs6jA(iGOQ:H~]w;&@؄&]`b8bdxhBbj؆nh\IHK\؅w'#w=ȄugSU[ovw y|#<n&xyufw7nh؉Hhw7gkZ8X~Ȉ'Wj؊1ౌӌfzpXn^qzhfppx'yfovI|)H8hfxoxHG^ɍ f yn iUx6QxIב){\$i&I(iIn,ɋUBIw4Y}};Ym=I,BBY(%)[xXZ\8 &(.9UYaWȊݨה$y4CIWGyR}}IU1xwiǗȘ蘈;dmᗗ '6lIIi7{V_vDUl9Y)ߗ8f6əxQdx my8cbaaqרXHf]y虞깞(HYG9YnjZHiy})5H{Ni9:|ژ9ƙj(cv5zҹzk0)AZ6!Z{.Zm!*J{Y֩\BIwghP*Tzʖcڙ-@bV՗wZ)&>)z'~ tG}/ɦ\z_Jpȇ #J^Aiql:r'C*^DV*::`2Ȩ?ꪑ*JXxB}*G *  *JŊ7j'::؛mxZ(7ڮ1~ ,j:9 ۰:ꦰZI7jujxIЪ5ɀɱ8cE1-{/o!dD9 ;+I0d+[1KUjdjfjGkzv:l*[ ˭t:];F 8:k<>Kg:Weg+iKk m۱|۷~I7fIѤ7:1[{ a׈"yD۳a;" _K[j9mQ+Sv|'y)|}+|y ۻeDl˪kJ{ۻ둍P[UyS뽌 [A[[DeqXw\{ Z{KɊ[ߋL+A_ˬ++L  |̻ ѽ+$\'Yأ:7q65)&L j *͋.PLxRENKH ]y_bܚd|Ř^{ Ƈ7;- ^Ǧ1ƾMK6AǍ;)׎&)˯, ʢ|543n ʎʷl˖|h5lPkRgvlǚILɋ%;^,|ōe<Ŭł lHͶKǚ{LQ6G%I\,|2%kθll,Dۋz ͸,ͅ(;Ѣ ѶFy̬<4]8X%|r &ͼ(M,]H$C=*MͮGmU lOMSmcOed =|dj=86OlѺϖnۯt]-|Mpׂ؄QF|؉؍ѕl״ה*ob}@BΣ[1 z hӬڮ uo.Z՟lu`=K}ɤ}+yI=MԺE]]NP=ݡMTFQˀm<ٴ*' -ڝs1}==;X Z\= ;D  J(zZޯ*j^GJˠǝ ݴ&n^h|5>؎}QEHNOPR1oQB7~ ^B$>~E-d>m*l_f=hS<Ɍf 醎`,{]^^鶩~%7⨞=Nj`(N9>ָֽֿ~ü겾Ȟ썾>}[.NN~̮>|>]S^88<񮰎|N7._ B]1\+I???GM21^~ oF@9$Oi|F7@BDߎF?(J}z(9v;[ޞ\OQ`T L{/Mo^b2?OA;./>Wi??_O_?_Qƿ?/_ʟ?O߿/oo_@ DPB >QD-^ĘQF|R%MD, t9ˉ SN=}TD"$Rҕ- Τ3&D qŚUV]-zhҥeK658LmW\urp@^}ɚ]mԧm>]ƍ?3_z]@f_z̹&ȥMFzre^<,11ũmƝ,kʮ[qںG|j{}?$_Ǟ]vݽ^̛yYߩN^|ǟ_IKNP{ocA0B /Q:C0DG$ -ˮ3i1A4G2HS1C [TEmq% ltLG!2K-4C#d2I5crLD-߄3N961M;lRO49%Bd 4,Y;ifFeOyhjnz' S [lKZ.{[n{%;k'pQXgOoּoC??pWg}S^s{@rLsQoI֗g_Og>g@vU?YP|z;&3>xh@j6E#[gSe m=Q#R%KMáO*mzXT[Rc:՗4:uk8b434jҬ~ºA[kw>oTi>;Bt[QE*Er*:5PP UU}k\O Q.5}Mدֱ=,U5 VVmzIn.|HR,JCzR -C}&FnvC;;nx;^׼Eozջ^׽o|;_׾o~,EӿW`u I&ˏ7|ր@,~s,3ch@E<`°@LbFR<@ DP tB@\Gn#f>&V{69'N׀pi > ^ QjA w%1i^w iygts|:g3NmH(9 nrA7P'1x,@VznZK$7xXK?~6ɠX}$[5_ :˓ _Q0{ٰ~~!"JYOǧn5" M}`(۽ll[ezM_3:[z;wh$@zl~ f@C.!lL$ǃ33K#~!Vi*gܮѲ5V-`9y7D1yc h+=)l]T˼/!axjg @.;vpS򶋷h$V<py DBvյy}@ID 0p}:T,l <(+ZG-G~ ^)v/uOD%fխ}7@?z[?}u֏=Xtj_][ݺޛ#+[-# ,굛2* \cTg,A1`#09@=??*@+#xS:1稌kAa'* +-:< ><"L2ޘ.Bj;#6܁r-Ӱ߂9۽@73l;ß;96tÔ;)'+J)@,ܾ'©T3DDE$C#ŲQZ3L#:AF iZEdV+fS?YŒEb2[<,Ҳ S!ĘdsaSğGKs6VSx1  @35c3";!#,0HԳ1G}4`q4#ȄDȄPԙȋLyЀ1\{:%# ID(SEzITHwɗIS 2{?IIKJTGkDʧ^i3L,A $Lʊq0d˳t 1CCKqKr˲!,22$BCM ` *Ç2H1Š*DB1D$#RD2#KFƄRdC(_|s͟=i ec~*(rIu@ԩZM֭Mr V،d˖<[1-؍"( 1nEۆѫE!, 22$BCM KLWز,,8((433?CDN..: abluX* xa @"Æ ZQcD7NbGD4HaJ,4@*I@(0 h4 Nj,q " XT G xSP ,`Y *E$ @ ,HUhp0 !,22BCM$ ,,8ز((4KLWCDN..:ablABM(p PC4|0D.ĘāTD" rq*+Nt2D #Ҭ aΝj5Ů^n KٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3kϠCMӨS^ͺװc˞Mۣ'D={ݱ*`\ <͝C(mtʭSWx!5IxO ^bgK|@p &?_ms * @@;=u@4Z,@ \ @0qqX!,Ww7o22RRR222  $KLW㲳,,8((4BCM33?CDN..:abl@ADH*\ȰÇ#JHŋ3jȱGBIIR8ɲ˗ œI8k%ž@K& J4Т="p)ΦNiJ5*ͣV̪Uс\g5lNfώM˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹sCMӨS^ͺװc˞M۸sͻicK50 9_͕}RvX~;6~phP< ?wA P@@@ ڵ0A%@<`_ <|u!,Q_522HHH(*\Ȱ#JHŋ3jȱǏ Cٰɓ G\ɲ˗0cʴM3sɳO7 ѣHҧPJڴ*ΩXj:Ҫׄ\ÊK׳˪]˶(ڳmʝWxh޿^̸qĊKL!ȕ3k|ϠviI^hjխc˖۸omN@pœ'?p H0ݹuХO~l{Z{tޮj @Ͽ(h&  ߃ᄕIHᅍYᆅizyry8,j%@+(#V/7S#<c@`D;idN?$CBTPdTJ٤eUf9ҔZv^y~4hhlf=)ID*| !,Dl+22EEEBCM$   KLWز,,8((433?CDN..:ablHp`*\ȰÇ#JHŋ3jȱǏC ɓ(S\ɲ˗0cʜI͛0Iԉϟ@ JhP']ʴӧPTZԫXjUՐ_Kٕa =˶۷L׎$(u˷ߝ*Kar≯c?L"[̹35{M3搢K^}thְcuMWliL墨Mx+_~4УKNμwܿŋ_Syɫ_Ͼ{Od_ןhwx% Vyz Aކn%P` z]`a(ahx⋖\r㍍Mތ $㏆%x@Y$H5P $)e_1I,\U(AQvifYgɖ xTR!,7y+22UUUxBCM$ ,,8 KLW$ CDN_ C((4ablH*\ȰÇ#JHŋ3jȱǏ CIR`(S\ɲ˗0cʜI͛8[ϟ>IѣH*]ʔNJJUMjʵׯ.wV;`Ӫ]˶[bɒ5ݻx9@\{ Lk\҅[ǐaȘ3k~Ξ6\ v>ߟs@a$h&D'`=!fȔj7q!$Ȓh`&A(/b|@ 9Q(Ɩ-$4(ҧH_2:ӠΫX.Tʕͮ(=UXTc:-%ƴpGzJó]Ht/n-0[εX_zƳ|mfV)Yh)3œzv=uwQ<m_ɬ.9SՆƍ·l5mW"On6C֭zopñg;I8uy&W>0_Umh& :gVxZW߃\J_A asW"zסbH;@3V#u0cOArԣd5YEvj7w"R>bRB5$UvyYR%Oaz!aFeѝ_JUeng,Yfiv5akl>Z'&cuQz%fr)ꨤjM}i!-a~ jC*Tn:+y+`]+'z, *k݆X&kuا{^cؒ7]mY] }魖Kj'{ lWlNK2B N#1;憌0,̧j${nf'd<6b/ C+X48㥳<3' s\]mt{5, e"W,T/k"oِ%}vb=7{|}ww1}Q,jWfwՏ ^7mLn[B%G5"?&.QK{>Nx>㽖j<͂*apG4ZR** Zs,'C w: q3–=E1|yZ 8H'c"xJ0RcD261Z\J-5?LfVV(ٲDc殜ٻ:D4?f3K"CpZR5Htk*d3Mofs$'͹;ng8#Jt Pŝj\_~m:g_G3̢Hi'YS%6ij͡(*UCUgU$6Օ/ KTJaVŌ= R3N4iPڵr-ESzG Da4i I, ;UpF,' JY@;O۬$D:ѴPl#\=z8TdmW5mkRKڰ|l7+ٵ)TH▗\uz^0-r\.uyͨoY!܆Jn ]Jtgݪn ֔o)ߪ{,k^% U&wkl3yafx- b`wڂ5k]cw.0)IO <}/AG_/qډrة=2h'W%쉳j|☼-Hk(U2,`kM^V9Oqfs'M)Xq}bޭ ]pt57wCn\mbn.OMGyߴAiIR c]ngm^>җt;A/zsX0Ts:?ĵ^qwx񼿞ܗt}vGͣN=o~wpۛ.@W] v_'~r9_K~'khG1:C^yפs^ì6}K50F:ݛN?(x8e?}i{ֲI/vK> o׋^~~q{9.fï_>ǣ~V}~&~'qwf^(owWx lwx{}(iȁgz 'y aFm'u~׃> h}3}5HX7~ȃ0{V8v |HiavX~RhVH$&vjh~`Hw~ׄBH~wo؁qso1Hʇo#}ζ+xpeh/NXnxynHfw"gf FfXGIȂ肎GׅwȆ(8&x։Fv{ݖ}}؂LuȊy|臵Heחm¸׌XMyv_a'Ljh^zWhaHmᡋ*U8XxJH((hوHx7,Ȏ ȇyň,ْItEx( V+9z iy{e9eHrmukؐ8GHh؊P&(USy2דS)D)eYYw4)ٕ uipOIShyؗɘI.ٛ9Yyșʹٜ9Yyؙٝ9Yy虞깞ٞ9YyٟTPz ڠ:ZzڡA #Y&**-I 3H j6Jl?£:Z ~ƅA C @caŅNzPJ\HT:KڤWڥE*3Z\!IjjX\ڣ>:ZCzazyJ~l 0YkjωJꛎ*9>ѡک !,+%22&zzzH(TC"Jņ2jQŋC`Ǔ(90KʜIM+s~gL>W 0I_iʒJyՙH̊+ׇKtԑAɒz-իpouNv 6ٶ05pԷq Ŏ;i,|%bŊ#]b+sYPY{v6C`q_4WZYg~ݑ쐟m_ 9bݾG*{8񲱏O={q'\% 6F(Afc%zG`QUn9Xad2aOb{ّF/:5i)浢N-TG1␛%c X|eeuX$G" @K2ɠAIm`'vԃ`%fjdY쥩ݚlȥ5UaX'uf:疒X$(iI|>fߦ+ q)$Mh뭸 i~DSv R9+3~YiyF)bwr'j:hV_'aRiu[жv i/nI&iao4+pwsZ"\k ߋ1B;)"ʰb/,!6/)k2-{cZL+{r9s=) =*TWmX[MX?ה_lFBͮԒ _yw0` s2fo6j6Wn1!v fU:ts;VJ oFfb`ýf{pԕGŸҎjNsvrr,CN6uJ{y_^g?E꺿|c;sJ?to~󧪆KnESSvbYq';҉tp? pݣ4-[}ufAauǓVE,XގT*9a% gC:TCp +ZĚիklJ綗Et ЃPZ{OG:Y4*K]T[/!B eTD.r x?9/\W:B~p~0|%+h bC#&6rЍ@4YS[}\#ylDE鬒Oi#bwxt&KXcj2\-"P~/G5R~%EI MliD\`\#;NɅѐDd11WO>%{'|)AbģD'JъN̊j_0?t(fZzL%:uIH!J"+gKlG4 -Hϛ3O_Ȇ~3B-)QOjFnAq>#A5z4aQў'Z]h;*¯0o"Mu[`?)(Ua7fMJyV<}pN7 wMy׬N;vÉ Z@p̻ٽsҏɎj8F9^PY4:Zu p#~'yMwϝu|8-r;=X11l g}~%SP7=؋۞Ywcoҵsynև74{g7e{WGzu2F~oWs'of~h~S~W}x}q}7yp]'|7uنoRn\JYWy%xy{e||i~7|dw|2{xtJPfsmwE||Nhچl(w8h>h Tzd7hz^8ǀۆzIq(؂݅$|4i6Xt~|#8ܷtWx\({H{Gh~8w Cde(rWj\j}`(~ Uhx]58xG(<8xt(،⇷ȉWx HG}hDg082(r؁,X.hXxxx@hWs(6Ȅxȇf}Gh[؃ȆiX獄HDmh!fIȒ&jx~Og<ǍxHIk>Oȏ yCi; 8xݗȑ8؎&)؊%ybTVYo9[9]y6(X+B*:)Ie}蕯XM bȐ蔼H|_K]Q9aq٘=IvYA)y/Fh&9fn5њXiYə4 %AHҩh-t KA*hӐgJիub Ю2TbR&e钪ۘ`vzܔH㢽*Nik]awD\ݯ`{F?>V`˅Ur-D9oTo?vbXMESe=n-u񆌃nw& ~!^qm7_& 6 E%6\ݴMze8CEE]%_߆uH"I H@3ZfrR,ZqXQda!bi'KY֐NjՒePe9.ek\:Y#8izfnO֢t/cI'{MYҏzN#~ nIhڒҘb{:nGzƉdة|Y\TmNff*k&{݇k:¹_jꗰI*}aj+J-FBXh-;ZBJbʮrm7*t辖+翭r%z+VY"\§vCw fRzS9Wؐ(4j'?ح<窯¼nzKTSB<5+l6Q2wEOwb$=v|ss\X-dm6h-8u݈ycmYgk}NuEwz܄o|. KFnW^8Ug [0l77}g- W1$8ņ9+=m觭>ս~I^M U:oP4h~ϓ'lL*L#6H?I/dHK>̥ 'Jq 谘+F\n/ WE򯑟47fɰ538H:^њLb6݈A]j?a/^ >um6Q$WFzT=5ڭ23}9y"( 7EdQτ(A{)Ph(-JRG dH1/f:2o\.^rTj,ծzU$OejOIp?%jTjϊTX ʜmHjțJstK ;AZW+[ݕ25){T8vmMdJYrx}Zzv9NHZV֮PZTvD=G;D[ְAi)X ׭"EvMnj۵ֵ.d͕bunqo>H}+`Jal$z0*M[F-fm<֎l! `"x]pjPv-3{asfؼͯ 2WΥ-<߸պ ~_aWSpAKL]x:3+ǸM)I\+) Iqz?'T4ޤ_žǃ%iP59}MBK:_*/nm,_Ac6sYW<4gȃ~[{r7yG߹R(.xMsV=]_g?m{Ѷ+t}.W].6~=/S6vwxgtgpdէv0Wx|7wi~ lra&Gp}} gzbG~H0{g'|zׁW~)u~t~]'~U7~:}vF)zDŽ儉V$& wIK؂vpp{ug}iȅQ)G{Dj׆yyCH#xׇݶGzW(?HAp^hdoXhy}vGX𧄜M(؊GI~pH.8x^Ç(X8lTHhxx}Ш*Y*gʥڨ:Zzک:Zzڪ:Zzګ:ZzȚʺڬ:ZJzJq:ںaz㚭QZڮ:J:Я JZ ު3JR[F k(5Dyz"; [+0F!B2K@'M%PQ\WKL[#Eۮ R) {@;'+Uq C;cL+_ O a۷+ "{aBkpA@u #k Cuy{o I[Q{`3˭5ָKAKL{;DKT˹Ë{+ȫ뺬˯ڋx[+65[QKЫ˽{[ɚd+۷;=;_pܻƻkm{뾆ںcJlq+O;d[< _˾0\Hkuk)+H ;ܿK&[Q+{BDF|Iܪ plĪ 犮-`, ;ƞ!,/22xrxx)lrrvv$$x\ IIgqqTij= H*hŋp!.Q;k>r9sM!,122 125    '(+ @ <Ȱ@ :|0"-^ 1cD!6QA8N0IBG)D`K2HP&GqC @JiJT@*W L V re+ܸJ p]w@pa00@@!,"%/22RRR 222@AD   ((433?BCM..: ablKLWH*\ȰÇ#JHŋ3"ǏBiɓ(\r!˗U„pM5oΗ<{i'ѢC*=ӧPJJիXjʵׯ`Ê5U08VjkF} ԥ Kޕzd` {<دbfH8 Z!{,_G`Cd  b?G@&4AB !+,"%%22???125    H*\ȰÇ#JH ,6L F t0H\ɲK,p͛/c$@ϛ:wIteP5=*SP8jΝX xjԝ t*hŎm 0HXk38@7]8x0w%È+^ `@  3k>,d `L:DPuӝ]YviڵmkƝ[bޱ}>@p3Nθiw :t \.=q@!,":22f\ xxyy s{| $$ہRk}}Ԛ##99rr [[h؊vv 33TTgg''55II;;0ssGG:; f̶d̷qqII%% eevvHXȰÇ#JHŋ3jdǃCIIS~<ɲ˗#UD͛0g$ϟw J(Q;*]LPt*SիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿݝЁTPtvF"p]Ha44`]a5 x3 0" #G8x@h pcF(T)EUv9X% xYa6I%i&mVoۜeyJIcri* Y9塈㕌.d )j atjAtX*HGjPWz Q Ů`v`j ہ\i5PÞ9ɅW0J䛷9۟1*Hի0Ē(;2  U+򶭚VxP irMlqc_jJb @tR`Pu TP:@UvT2Cn ~@VNu$ ǀ;6rTa ;A:Z8)x!Ox%WU( >(P-u . lait F pBSզ !&J:D& P\!QD DHdE!,1 #22\xxf }222|} ))??__k$$**vvyyLL[[(rr vv"iiIIssyyhs99##؊~ԙہGGc˹33NNR;<f̶sѦ >>/TT<E[dwIbD ef=!,"%22&VVV.\222z}JrrE]` 08mt/QŊd{$$yy3؅LL __ii]](0nnJ**))xxE3vv.33~g??]]R=[,|| :t{ __R&TTggSƉ`=0,,{{ II1 HXȰÇ#.LHŋ3jď #vIɓ#CT˗0=)2͛8K0ϟ@* ѣ7DʴI;J*JX:Sׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνOӫ_Ͼ˟OϿ(h& u . #ڥ ta'u"TjDt (4*s䘣@iCDiH`9PAAFA$i%1cIU\)&i9 +]\+kZ ¶v- +"A-*{A ,*Ŷ&0p³^@oIpS, h˭EQl1Ț<- tCL KsLDmL3,;!-5J@A40 M}\-`AuSO 6(A\O7H u^A -wMPD dxxFyWngw砇.褗nzv3N@,>;N;n{l|[LK/>=o}}ϼ/W݋/ =3G/v+@y]7 {:=y[(-_H(` Ao`S?Oq|8Dvg80hPk ';oSo&Ќe"Ŋ1x m;h5oyyΐ 'F06!,A,I22f\&222 ??R}))__ sہzzkyyLLNNii,,h؊**[[( ;;0}Ԛtt|}  f̶;< ꎎd̷{{ H ABxC' JHQ hܨq CNlɍ-\)ɗA *g7_@ϣ}TdG@p) +*ʕ+ ŠK+ŧO ]A۷pa]Цka߹ͫWpvV^Gxw1c\2`0 n+9Au/;"q=eZ%J>ګн[ *"L`K1cAhgMKA۹ex qgz<^z%<0@zܗ_eQ5 ! (UB 1#0@`Yxj!,8"hp 0@'cO(=*XAJ@ 6'`)&ݐdИbRdEfAl9myLvw 蜂j衈&袌6裐F*餔Vj饘f馜v駠*ꨤ*)"Z +^Zky:魸!,G0Q22\fxx222 xyzzxy))??} $$ yy33 __TTqq 㑑vvmm$sہrr؇NNLL ggiikII ,,}Ԛhtt56  >>/Rf̶ <ѼsM  ^ͺ#"FE0m%>/vv" SƉ mt;<ꎎuu {{ "IIQŊ HXȰÇ#.LHŋ3j4(LjCIǓ'K\"ʗ[ʜM4sxg@ySQDm]3)LPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_</bdㅊ1 8TH#xpzW01? "c%_ 7b Vf! h:| &È$n)n@ 0Ƙ4 hc9x_=B! A I>xadH~v|u!T*pbt`9di ,`A ؈ba)g֙%"r&ijF2Z& ``( 規 Z*2ک$JiX'@VP*' aPု~q C*D? 0+ L\4l eAk4Ah{<`d0t !H0L wN fqܱ$5Q@\B gl-m4ls85:^?:Ѱ6d W}DtaJ8JO~DDuC/ˠ1D%'lEЭw<bb/_!gFVPt!,mDd22&\fxx222 } xyyy))vvxyLL?? __Rii zzIIqq,, k s$$[[(ہNNrrOO+ggTT ؊htt c˹3356 f̶ }Ԛ{{ 55H XȰÇ#.LHŋ%jǏ 9 I#SJ<ɒʗ[ ̛kΗ= TѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KJ" P_ 4MznpPFµkذ`6nZ`ݻةv(8tIfNr[,ν Zt%@ 0>/߀ 8 { x^"x߂ Jhz6ǕAA~(sAL0HbzIС8b"nE0Θ^7⨀AL  ꗢHS d\jyR mF2!D % lpI8.Aio>tjITꨣtGP@S,裠 QK]@<4z*뚣4Al:tkpC `ү;ӭT J e7PL[ 6zy,`CIL1:0'pg6-Wg0kL1{0[ %sK_WF{PCmГmH'L7PG-TWmXg\w`-dmhlp#TB/@ T7s}7z-PzT߭-ჷZxv7Nx/MP7o7>{۝~~勯NtwzAN{Aλ|y?:߹./~z~׃o^/}{o.xЛ⸇f~?wk_@Eys?M~t B n{BNR8Cb!,c_22&\xxf222 R}rr\ $$ vv  33II xxTTgg55ks`~gOO+vv"qq--/rr**))hk؊LLہ56 c˹]__/f̶mt ((,QŊ}Ԛ {{ d{1H& Ç#Jlŋ 'j8Ǐ9 I#Sj<ңʗ[ f8ڬ0}RѣH*]ʴӧPJJիXjʵW0x|iACp­ڙw,A+.0F z^1E  \q̃yh1rF"װcN+۸s=€ pȑ^;q؝C݀AQ[μv "x^w8Iܿ `z=SnHp^V `r vÃFH`^\svx GN:<t^v Qr<Ԁ~2 F(:PAqp ?1U(*DM;,!.( dI)"p/(@.(ҌDVؐDA @ȠP!ie1>.ॗTtd%9$VZUYdsB &'G `" p'0( iGu駞D )G{*'VݭZ+Y+Q [+PK+~Q, mpNKm i*r'зv+n}+k,l' 7G,Wlgw ,$l(,,0,46s8=,4AmE/sNtBCM-g\w`-dmhlp-tmw #LwL+Ƀ ^7.!,%22&f\222 ??zz))__sہ|} kyyNNLL Rii},,؊h**[[( ;;0 }Ԛ tt f̶;<'(+d̷{{ ێH*\ȰÇ Hŋ3NȱǏ C&HdF(S\ѤK,cʜM4sܹO< H4SӧJ*B7jՊ֯`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3 !4BZ"Xzu Le࠵m.&oveµ`Нij&hν{w_}f'W",c0࿗@P~Rxg(h JDx LXa| "Сz ~m "tJ(7DB)DiA0j5\Z]X)9dIbd)ReƩ@ĩ$)Bԛok>Dm*0B,PijL&:pFHJifęg`C5$,0*X6|J+~JA2@ 46~ Bt+:lpȮHL*@cۑ*,/GG|ɂD[k{$;+Nr*r. sPL6,nBA .-_|ٟ>{c>'Ai 40?n_GjP CX~,=HCPԳX@!,>22\fxx222 zz xyxy?? yyvv))$$__ii,,} IIs[[( 33LLNNہrrggRTT qqk;;0 h؊tt 56 #$) f̶55}Ԛ{{ H*\ȰÇ#쀂C *p8AǏ C R$P$cʜIs+s C͟@LΣS*]ʴFٴU2ʵWJd\KVhӪ]ǀp•zDٻU՛ܿo{'Kp1sʊG`!A'1:cKTh-!mohQq^ǜK?hس H|-1ӫ_}PS2 ͗oϿW}إt%87LJ=堀GUa!QP=(`@<"^xSQ&~DK,⊽ICؐ@N؈#o:HE C91!,plIf _^)[vdiBG 93$PCbz m9 8@),"H PXB>@y&@BD6D=86d*ꅲ_@管w^Jl 6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg=+0$sZub5!,%22&\fxx222\ R} rr vv$$ 33IIxx`TTgg55 s~gOO+vv" qqk ؊h))rrk56 ??ہLLc˹]3mt__ /}ԚQŊ,,{{ d{d̷H*\ȰÇHŋ3NȱǏ C&HdF(S\ѤK,cʜM4sܹO< hH4SӧJ*B7jՊ֯`ÊKٳhӪ]˶۷pʝKݻx˷o8t  (*Fh!#C*H9r zL:t ^MF@~˚u#`Nay6ڬ9WKa&_sEw'*O|i XϾX1Dh`|Ч߂  ЁR`} >8!:T͇D}` (c{E(*./¸Ќ@Fa#"~P?)>E U( 99#RGe9[r2*c! =1}gw&gSTYe9U%7E"Q"(khƨ@=*(LBu)4FAnV3$jT`+! 0`뭗b$Pb +B{,ӝlLB 0fl0 Bj.F C$~F L@j n[]BN ou/PM$jG1,Y djU1[,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx_|߀.n'7G.Wngw砇.褗n騧ꬳ@}a;ױ@^'p`{!,/22\xxf222& }R vvrrII$$)) ??k**|} xxLL33[[( ii__vv"TT--/ hyys؊rrNNc˹ہgg f̶sѦ>>/<qkw՟x W-(ff(P!~AE0bN<@N`3ފ ($J67>$<8DVc)P=@ D8) HiV#bV$pifqhigl&PA7$sNvlBgJh I$U-ZB&Ar( &$`u(Sk 쩄k;d.ˬ.F+>ab+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|Y sγ88!,%22&\f222\xx zz|} yyLL??rrR ii}s))**$$`E[[(ہ__/TTgg kIId{,,;;0̳  vv؊htt#$)33NN ]3SƉ ;<ꎎ55d̷{{ }ԚH*\ȰÇ#J @ŋ3j(Ǐ Cɍ#S\ɲ0cI͗1sɳO:bIт40tF5y՚FdH0+W +DLxY$40ʖk cKx-bݿ Wo[}^|c5(LeQj0̙ Ϡ ӨSװYw]34h; ލc6h"y+/%ģ4EU<.{G]\ǎv?YxaBa'`A n}B lF(P!x,țv&ab uP$qF(0(4ˆ cA@x%-XH8;ԣ@vBF&i%,$>e "P9dh–Nv%9fp\)E)`|%A<#XL@z', @8A$@&ޢ&@` JXjj h|uXj@"@`NܐBj@vѫT!@plyrD0-]= '|5($AW@.ElPhX{6_7L;bWlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmxx|߀.n'7G.Wngw砇.褗n騧ꬷ.nd) g{O@P|;7|!,q%"$22f&222\ R} ))??LL__iikyy ,,؊sh**ؒہNNc˹sѦ}Ԛzz--/ktt?@f̶'(+d̷((,{{ ]](H@,l@a $ JB2j %PHR %(Rc%)8|ǂ P OE CG9Fi bPժGK, Ҫ]l^HIݻ.+7ex}& bo~ $^|q\%+2#@6(@zE<#QwhҚJ =Y ] ]aW7l V,˼,'k704Ȍ[L!Ȃطs"ƁI#A!_Ͼi…3 ?g  ] f`- m`&0$-xawn DٍHtCx9-z %$u`BA Y`F R4ԔWVycCf%L^~ R!,!22f\222zz  |} ks))??LL__ ۋ؊hہc˹sѦvv"R,,}}Ԛ k**ttyyNNiif̶ ;<d̷{{ H*\ȰÇ#Jˆ 8\Qď CID <XrLʜIM$8@ *ϣ,[]ʴiPJBիX^u15ӯ`v"ֳYsۛc@Kj owu8uFaĐ#웂q%kFWe2og#&4dU=mq=ץDеo wz Б4;ν;+v0 +Xd P c`0A 7&Oq9sFΘ  B8= E;@ڼ1ˋ? ;kϾt0r-l|F hXgF"9B`P%H%xԭ[?ha 2ܧWAXN B@Ud  Nh"`g@ D.Q@!, \22222 H 5= '(+(  *aÅ J4q /^Ǐ C(H&S\ɲ˗0]&@c4kHgI@ CXʴ p:@UPNe*jӬZv:lXdd`W!Z W.A !,xdL22&#22278B >?I-.8+!!-56@MNQ%%1wx}  ,QR\?@K:;E󩪭##/;I<=G89C9:DH*\P! #JHbA bXȱD, H$I4ɲeC)WIS ̘5s9 @=`DУ~0ӗJBXJc`nAذ0D5`Ag<Q qςb=" 8+B0`y],sFHLӨS^ͺװc˞M۸sͻ Nȓ+_μУKN譯k~=;߽[o]?IbbbȻ ꪪۯl@ ! JdЁ%&dE $0$˗0UYɓ)?̨ G S  UhqӧPJJիXjʵkWpufRMV*۶PHṋ{ATtx@!})Xx `6@Å ;~!{),8JO0|@jw#{!,22# $ BCM>?I & :   #7K ! j<+e-&7123, #H -  "=W)L‡hĉ+&$#7vx@ȃGP`$*Y9̐4GY1H-Dh9ҁ#ta! H@!@3"h ! 36D .NB*0B6DHAs0bE޿(0B+6,qb ;<`Aˋ);fĜ;/p䁥>ش!,4-#22kv< #GHS>?I&&2P //t))5ϔ2{2~EE,S,,wշw{22?@J++7400ʞM[#zp Vz_N[ >@!@̳4X/] x0c˟gaA *D|iGuh9T  Xy@`vda8P q!sf8.\ B2a16a" 0܏\&i%tvCy!,d211111,73 sg K  !+!          ! $ &'($#"#$%&'((&"+-, ,!-". $."$(''('((((((((,,,/0./00*262>1=*9#8!9= ? @#!='#:)&=-*C/*L1-Q31R58M:;FQ8ep)#FLPPEn:EA#&SwUޠIS"kQM@X]uS9gdF֩iZjUjZ^7VzbA)k ,lGz O [hTMW UTGHe7La8AB=zgjIczZ wUe|mQ˯/>HS[yVlg^=р{Z$VRKseީ*On#_z+iڦ7rGl7\+nlXgu}ΖtqhX }9z0,VS7HcVwgkR*?)Tg,#q2V{wu=`#i6c nЈTF;~5B]MPU:F 1m͗=Q 觯˾TyL?TxuɊGL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢Ōt%G.Ňݪq}HHgDlP*:"=cCLcDQ ѣ(  iEQ< \X:uŌ#FIbMl#+Ȅ\#dIF~K,5XXRe*Z.jJ2#()KD#|2ӑERfY됡$|.6ԨA+,H&89JL؄NPR8N(*hWx \]]|  3 n0`v؁!hX1؇~xT;xhC{8XVȇ-H[ȅ Q  nhcc r`ø[xcPwhyHx׸ ЍݸЈx蘎긎S8YX#X(0zP  ( f8yȈȌʸȐ[ x{,HؒhHqȄ;89)Xh0 9h옔J)@(_XȊ! | 0]Bd`yc0!jim iyrY%"H𨂙y.&H3;Q'HIy4ؔ*YQ_i(0"%mdrx s rpoʼn Ixٜ8ȩ '/ɘ8M簞멙NI7XE?ى( _)j\:xyzn*$Zp)a-ڢ,*.ʢ4*(j5 0Z>:>DZ>PJLɰP'Z0أ FCYڥ\Z:aC_ʥglڦnpr:rhwq_ VzP] Uyg񛶩xzʁ!:Tjx;3+*:0HJzjOj&UʣeZ^JaZkJҊ::| Zh @'ȮI [pPzد ;([޸ k p۱"ۃK'{-۲ 2˲]3۳>@B;D[F뱛`ۍ'ʡO Q+Jzꐲ{b +e a{d+{A!˵ޘ % ^l˳; 64k~˷{۱I\۴ ۨjX{p9<鰓=DL@}V} \^^]0d]d}/=\`npr=t]v}sY{X5(ϱ0ʅӍ`Ɉ =ī|ؠZ@m٠,ٟc ڢ=ڤm Ǫڪڮڰ۲=r p۸ۺۺMo ڮ-ܿ}ȝʽڰ-s؝%,w9ʜlyυcޑ=]]=ߠ@=K Mʤ^  >^n~Kz ">$^&~ .,|'˂^)ڝw藐ܜJcPJ^fJR.PYOYYXW^Zp hj lpt^v~xzy>|>^~v-N-1=>  ! XBDNř ԩ la^=U~c^尮 乾X~R` ^~ >^~؞>^ 0N㒮,|> p! 8%@Q2\ ?_o`_֐ Ş n$_(*,.0p(O28:<,oN 0Dm0.^D 02 MQ| K/@ k~D/Cxnqr?t_nvyyoʁ` N~nҌ  o׀ ݐ q p q` wר` o ȿ?/_o_oO Ihj*8 Nb:web*3MAsI2A-SL5męSN=}TPEEM>ݢEj/l] V]^UذbCVyMVܸsśW^}.މ FXbƍ?Yz3ĜYp"vrFy3葤@oYSҥRڵmƝ[n޽yZ֬OղϦ r͝?t˥S?V{ڶrX͟Gt%B ?eQGHI'T$g2SM7SNBJ[9cbK f9Ađ9uC acњ2f9a-6Eƚ|^-Hu kGJ>ti%\sRueR<7^y)bbR&VGg p9/qpko9gwg:h&h{F9Rv^ :j|m)ƚ~QjR/ BrdX8dp6ujLo>KXk rfߪqbKrfks?=tѓOG=uWgu_ gGvo=wwک>x7Z_m]-x>?&/nスW/+|d[Ћ_cH!Ќg<xэ`8GtFlA d(bBJzwH0Gb 4́b( j`cc88;p#Y1tN' l|c2q%M. ؇=QqBv'~U|Be"fvc49 0Rhf6Uћޤc89NLiU-a(AILbi`@\1,AX`b0-rאF-Q07AiL-߂˚R/wY^t:f2wE93szfNuSND>QihFo6ՁjT!(D6vu#X cyDBu1Z0ǡT`/px |̹D6~Xh-06y2]?w&T,uI[J`3~ AtileKM 5FANU 2߾C;zԤqsjt}'UVWTUUm ^W]8BZ(ЪV *C[c7"&3Wv(,7r#u,d yH#8kAR4Rv?;0RbRKӽlq\smU.nW r$qg\c$WǮ[%\6Jȹn 5 D dw ^9Xy*9E. ddv/,1;+XűFcF[plkpS% эm=rdf7xծ;ZwÙm9a=kYO瀙DH%2sh#0 :ӨF8g#s]MN fɾ>zᑦ"fRQ>\>q-Թ-w˺nHC`;ޑ,ձuQk[77. QLCg7~le;GoT"gUJXr@6Cx0!*a+p$,qQ\EDQb p# A#fA{w ]B`F2я}P4 І>x0ӊ~;>zInZ`=ƿ4jn  /QdGOV 0+JA܀9]Ђ1K :3$B+B ⊲k.BB@/D񋐲 d $ O8lڽ;dC==ĹݣA1AFtDxmc3 6&車.?C>C; 8SѣTcУ-DE :4 5$#6l7b-@N`L;&oЇwh9>\9p0?5@9[}*Dc9\ozoǞx DCD-狺8k/aB"9hSd=} bHv}$Z [|&\ ])a"ƟֹF1@nlpȏq,-GHulB # :jkGX $-TII )$!JLHȤ̩ȦDH2Ft#JIs4ICL|šlIuuîɛ$HD,J ʸ\CRʺ#tJʨʎʿƧX;0Gt%dǵ܊dxE ޴G 2ή=BN ̸ 9,a4x \ȅ"DЅ,L2< ǘ˱dOT)iO EєO[$-DNEs&P T"%PNEQeQXP؀hwQЦP PQѾQˠ#E$URxuҢYȀ-Ґtt>Z SZ؄2E4U5%3e7}S7}{:;Sx:=8,}(Y8ٔ%[R0d}VYlm[ehYYڻۼ۽ך٣ X} Z_ډ-`8|uZ}-XxXr؎ n `^ap@_gj(ݲ] !$OKh}IPz=i[Y޹͉MYUעׁEuX\dpWEʭJ8_p׎ӭ>7}Y> ߑnH$OJTG9 30fV7[U`h[ ` HW`&Z5Z^XXd-XEX=}|_ia`,`V$J4CB്Vdeeec7F]67>/;c76@axaNa#VEfإ=܂ZǕZ%>bp'f > @8e,^-V/ 㒌 26EH㙅8dc=e^e7f>e=c(C6d jjfE^mEe_d؂e#~_%@` >*VeV`Xgq孴#Ud6b&fa&`8~օ^hfxfm~hvv i-&Vfv闆i~k隖lvX_rdM>b|iV_mXtXgx -b>}8L3Fc.9㳾9FhFR=vhf$?VYAhDiki6.Îff陶ȖlileV؜-ͨ:B`g ;mFfמm7f׆czpvm.6N6nVl>UnĎ|M.&ʗw~+O#:8Oݝ`潆kPe=^o&^iovvdfBV^v@s` ? p ?qpGqrnfڕVqr:[d~q/po=xf_p"Gd#7rns@Fi)Viu0O-w/o'pqU۲fXh6w7s8_8:9V4>44?A'4YDOtDWtDEFgtHJ7HrX kxrtE'R7SGTWUgVwR.?W/Zl1/\3o4eTxa'b7bcWe?dgW^4'7hrEtlnosX0PNPugvwwxwWF{xWOusu[u]'x 0em `qoxݖV st %p뜸x&,._Nh}rwy'q%)Ow5G/އyfxsxG כ cXU!my-?Oy~_qmwoٹ\Ce9yXqmk 4nH 4|8r|_b|o|`p'{/t=#2WWF0q5/P3kWzqzxݟ%B#DB%,{5aH>[h{Y3X:gcco~P rO|WǓbЅp p]oux Khj,h ‚3'pf"ƌ7.:N#.jP/$gF2eFJT&Μ:wN|g9-j(ҤJ2DARRj*֬Zr+ذbǒ-k,ڴjv֭9plb7n^:+ _Ĉ-V,0':}[>,1 K X2{h]˕Fwv}ng6լ9, \i]b;W۝[vZؼrK6.\ w-<7q#x,B 6|LBc3$M!9L4G1̈́M9 NXPSZxaOI5[z!!8"%h[n!2[cR]r^}2 21Grر?tQ,v" 2v0"hta 2dԠ-t'LHtq,rZnO8Xr $Í.<.Xs1ؔV5ƄpR(:SZ8 s=g%I%urHQ5x!"IR|Bt|{.H@CʁD#v$ԛAFe<0LaZ??1ma d2ud /a#"^/! 6(F042eH(END|a1ј4ZZs 7nljsD5 lF9']!&ց:猁8Q)nѡ%2ynŘ6a=<FFWyn{4X 1VƜ5&+Ta?>2dal s8\ꒈ:´"L,qkPf[F1&9Av`7b U5аt";=F+朄Vw#6Nn~qHh:q (c7Ix!MO:(}[9& t6BSVv5| =\]{ÿ\ٶ p[&:l&+p1Kc9{S#]qI'#VqVh#7ѱ)I"/7&#.3ѕ% Tt&0E_`q3?8,bXX\x_,G$x_PC١>Y:qu\|9:Hms$ɘs訹kd!dC·G9hLg-f~iuV_;׺۫X{#Q1Vp-Q8.OY鑜fмmXЏ?=Sꦏ;c/{yL}a]}`^!V^e!bbu! ^^!~HZY)!ڡ:4!!6 6N8"!.!#B"#N"%V"%JCV1%$"!b@*b*J+,"-֢-".!:b9/"00#1"Aô2.#3vq*؂5^#6f6j6v7n7Z#A=V&D;<ʣ&w>#?c$DFdD'VE^$FfF~&125H$I>DAD])M,T#2\6 55dCMz#7jL5MvcOfP<"e:J@B::BD2d=cZc?f?:W~RFeAeA~XXZ%[[%\ƥ\%]PF^vG&cI`!\E\؅>_1 15Vee6F5NfgJ&ec6r- C*]AYJTcU&m(8%BJ%o&D<&pp'nA%r&'Gz$S`>'t6!]Lcb*$1X#1$Of65z'x--ff#{R#jYj}gBfN]mR$B&YYB.(6> (WrV^es"D'vMX'vj,vJFw~g{VC6'6(i2A&do.$UX%ZU hjf;B(kfN)⥅fFbi3y(ifcR#^Ojciy'9gBe@''5)}'V)6*p^Xj^$*s)f\S6Av‰*df{gjVfh-i6**RDJ)֣t"":*&A`.&TjR\FFgV]]c)wP&ChVP"ybyg꺮j{k*έTjރ ++B*<2:+R@&†IBjK:wvI6dP6\Ş55"eȎlΧwļ&+:$fVjmk R(J⏗ZѺơh8vc+4NBӺk6l&CΎgm-^φڮ<.kІ֭vV\u`L^m6֌.xB6&z#*ro&/l<5C88!y[Υ豮J4iʮ5Үb#-|)^cB5 /BN/V/^//*pfUz/ѝv/-LVBQBB55B!p!!p+$-I45 8\,9|&\,փK([617,ow/9Cjpy{(G>f p5#E+n6&ȁB-A!Bpc#|A+#؂W9C1p-lj;G*n2'w'2((2)(+m'Ss{qFsdJ¡\Bi--v Ml1 e,.tB)l-1pl&`2pYBA$`#xMC1w].<;=3>?i6x?3@@4AA4B'B/4C6s>^!*F+g/-o-HdaFYvL7cg-,$I ™D1CgXc+$-tB"$#!"s 3.P@`"9 WwVw5XX5YY5ZZWu[s6\5]׵]5^^5__5_oCX5XZ6X:< D7B݃9toe/8[`>9>݃=̃<;\6=@>=C98h8>,n@Ch6t:6C6޷;|C7w{}9l}=[؏}w9WӆڏJD9G?Sjr8 u#+p<|?;En^xA<8`í<4?OCu>@PH3x;a7{ƹs9s;8k‰\9ΝF={.=kԹgO?:hQG&UiSOFJ]U֭S.:t)O'9Wەc;tlƕ;nݺ-.% y&VƋ!w2Cph5ˬ櫞9k<*ΜO{ukׯaǖ=h۬vk]91LnBݑHwy)I)W| S޻w6qNU}{Ǘ?_j[~=vlٳWՒ˭\+ /z . cK4 /<4D6> qD0APCmC1FB-3h6tkQ.k¹ƚǸIin'q(J: <ƻǞz=S9;wO+"2+OpELPA ǘvrL*tJC/.?N6r-6F2 c-ܛIbUY;UlMU)UfCiٝ1ixaf6nnuwove@ O^Osoд }m<4iKd{^U)B zף۴u'GHe 1:6TisZW?%._¸'qXr-[S @Jpvg. ` &%B9ΑudJe6kk H}J>(T e-DIN* ?#KLFq kc;"(ATj+}ʍ #@ғiLWұ]WdQ}ALFQ@u[JmT.|bT֜\XhaX.u,a:3̟ 9eTy2ժX: VюSSEOT[=:pȕq`ZֈL-G[w-ejI$gձ֔յ.UH'=+ 9E6+5oVAۖxp᫘}9_!UnfKUvY.h{]/غ6t YiMyL[m-A-ۅ;p1#oX.keKozE+/e\D&3͜fU:k&k0f?w&nz MlB&zhEGѽw . KTӓ4o5]mmwrSM\-ѝnvw +&D Ǡs#߃b~ab`cкGotG wk<&ʑLlv.\l~#jؠa>o" qs|{ρ>hml#E7ёA\G9pդH6nň]of_|uUǵ’ǜ̐^)SR I%1wX"88pMc8< EŅx6m6ۛIm~a6u?{nruwJ̵21׋a#~_<tlO-c\g.3zA=QԜ5w[~eFyT%5=*89ЁR! M.!a<-08PApOlLPToXף`o !!NzO1k .!> tA!".<$m-P1> ѰF0oN -\p*`V+GXa-aBաP/Q,P. tArpȾHf L S0eТ KB\Q q EL1@T/gѬR+iP~h10ّ sQmQ튉joڊw ۑ JP"Psp~, $ r$+[q%r-%#cO#h&q2'uz'}'ACv((2)r))($!BJ\Qa&d+b$,,2-r-ٲ,uq-r...2/r/R(QT+Y+SݾRr1!32Q10/ݰ00s3Um1NS2Es4I(* 31%95314Ks6i64Y0#05}S`gm88735w33{76i::3.ڲXs<ɳ<<3=s=ٳ==3>s>>>3?n0a0"aAE]Ea4FeTF fFqEEkG}G4HGsHt4IHF4JFtGTE4KJtA4LÔĴLLtpa@ٴMݔ@G,NG8N# BTBBtP+POB37N=4NC4N `.  2S=SA5TC @TMT=:U]Ua5VeuVKUmV`VuuV`WVUWX X@U5SXUWkuYu ZZZu[U[4\NiNT\tOQPP啴5B+C5OuD#uD'5SS5u @ZoTS!T5Yuba5ZVsubAV{uW/vYbEXCaA5bYEb-cOZ[[ifU]\\4]qVO ]%tu^uB)_u_\C66S e]6ScMS#UWvbkA5cucccuUml'dAulSUaUn5mSaVfkvpfqCu6xNW]P6^r%ji^uQuQu D?j S bbvU;uN.wmawww}5vmx[nsWwdF]6dXSk7S#6FdUVwWVWnw[gZ|Z||páqC}V]WhUrrW0W !X_5WU@DW!u S5! lT7o6VOw}xw5yAUkWEo{uuzEvvYyxWW[7f5f}ɷ[ ~t~~~B?GY$Wsxt)3u35 `vzv9xOxcyV8TIXy8{[Xn_eUmdaqx ?6|X}#"!\WNG]hwYi7j?}8v k)9XXaz9xw]UCזby{]TmY_UbنǶ!w#oa-ZԷ9\gY0kCNGI#W)Zy^9 ;׀mLזtMWv Z7S`XYZvUm˹bSW?zw9{kY[ Yga!yizOS[|= Ѐ9OO:z-=Wu١qCwyS`~7Zm+{9X3ZZSZdU7uiڦE5z_:?zZivX[}usƫc繖xBuyRπ.S# ~ ]B;`Mxh{UBOIzoE{`bZC`{Q{[u ʠ۽w`훾9"uo` GT״kP:iy[&U "8 .! MQ v(`?՞X%aѿwӓ>|"@R D@N])L.Ma~" @x$Nb.c.1K`@>dBYF=:dN>@C"I%J^$⸔CUbbbYf&Q29Hgv顉ifl=hJh)a8x9b'8ieTr9Ag?jiW*o:1e(j=nɩ*k|"+kĪ8 Nd> mN+g!iXOC PB 1D$q)Kˮ omnʋ睊oL 0Bj$z-0|p(iBF՟~ r"|bۨL!-a! 5[kmF19(mB+t)' O0ˤN#1ر$vj\Am/0$rM7!܍7ޑzWG뮿{^W&t&}͈k !Nnv?=XLO}O]4Jt+B~ҊۑBr~ BߏO ~78*p _@0p \ k 0O8AjSP@рl%8GpvT#hCj!4! Q*r2!"J2>biADII^?)r NS`^)B|b-?Ur4E#x2h 4LH hJsD Nljs7Ym>`,9ωtl;uS މ|f?!l3,?*On om^F}}mX`7/~1 xLn2LS4XȜvg Y3;PY6nS;Ż1o\WϯNs[-gA%.Eѣ EW2%r\P;& Ԯ(7akdcٸF+Jnv_pmt4rH34 7^pACހC\+Lqzw!{0ܒ84)p+/xK r<,7΃]<39 ns'}wBysk\g/ >`;^=B̷֙}'x:WP{)]ns܁+xuWN<щ[",aY;܎}ml2/OzMF0y`f6({9o.=z&nַnq׽6p_faث84/RegٜMճ 傁]>x @ `O\LJLWf'\h]x\g[w['}F}f}l|_}~"ERl4hG%8(WS6e{W`MdpffhFY]<9h9b@ bB)KGTTJYMgK bNXHd$l&)5[Zٕ~~idigjlanaptYMvTxzf|5X9~Wi,٘+e5IYYę^^)IMTfHh9yY')㙋99Ifljʙ]y| 9MYM9)f )FGU䩠Yh_)a]E%EfʇҤğ9_Qxly*% נKC f|B\*]j!{#:fu 7`WYWwʝ Xᙠ9j'm6Am֖mym&nw xnnFoFppc`tPtHWtopQNrs8pM 7F`qpک:ZzM#9Z*88ps*Jzꫵڬ̊JתJڭJF*۳@{I8'SxJFicPD1gpzDړ)kJچrїGXr9)֢5XukkLֶeE[^&f6Hj|~{jKָ1Hi{`TeeF\H\{k+lQ_J 6Z 0Ż ۹5k)nK9˺K#fk@˽5J\Jۣ+諾Hپj5&w˸K8dz£KK<`j_z;\j5L*k|b,*Id(\+LP>;v`9K[Dg}| °İ#c)\yt.fKd]n߼u貖յY֎m~NYYSMLޜ< NnN?..DٮN.NnXM|6礓;̄DlQ3xqH+r¸q=s=O(T"V6Nb5:tn*92FcViF@ZkrUXKfEO܃/>\\vZjZlyΥF>"*"*# #yak/{;-6|_*,6maG+mT#xbD/3OͰRm)n!;."gpQUVq#$a[yg{iq۪r&O6Frͨ2(FZ *x^O`{lKamvml6{iopp 7al#+^R^B" [NHeFR/5f$qh{w}:;=]/wI^% ‹:!{!W}w?g؀+&}80`> ?5P @>\ Ti<#VsC:YlxCPBT d+c^#X s h}eb2)fc_NEEa[żH0[4#<C0"#D:ǃnG@g=d!.iB"\r`$Q/U|bJJfd#b$KFXfJXLj"*݈8D#X3>Pe/}BF$S)X#K2=Kr1ͬ&P._fbFRb,*`3]J#Nxu=k [.e?O$*1t4#.X6.FyfEuơڛap>f5gI)Tv`iK]Y./' 1;iOq8Po \@41ٕFD(юV3Jr3+cBꘑV< :1T-iWʕ4OzWf 5&ʕFa LmTՎUYUtU_d,Zֳ,kU[hOS̩.zZԦ;{jt 5\+TzuyDVR0Ul;1nTQ_ژvsOĬ+7Yӳv MGU%oyZxc9& EGP9@<\"@"6{e"b'Z܋`ur;\91qx=x>x_/o?M]fZp:`t2|:zf-=`( _|cOnZ6~񞨯.d+Qzp/D[c>R"@ب>ָkؾ>8H=T ?6*?:?0s 1?,AkC"c$4HC@2AP`@l@d1z %  4#,\ٓ//B) ,S2:2&,  AAK|)B0:µJB4 A)C?T*D*//B4 Ci(8C4=5( 7l#89\'=qrkAcBCU\ږi-A$J(F"mrr(H,ILCcÈNO":QTB +DΆYNqLp (h(((" "Oμ,_̰ DOL䊯4c N <D>= &DN R!(@L2؍8 ;8=#mN9S4M 0B?=k7x0!77؂ i@{:1I:T :SK 9TS3iCU8qPUU:3 TK[풡Ө)P+R PN(05 *8PTjPSmMS6%:Vh;S;3;;ӕ]_ a)>;e;uES1X"*/R{KKT*R0ξ\RhfzRrD(,P M .M1p1- O2ԬFԤS~CWG\l3XV僋[X F $&UZNԀ =aE OgQMfZYiSq0RL?OY%1Dg K#$$L(h5' @0 --uE—%͘ݣ9ۺ]FB 3/P :$ąe$Ƽ(_-cDPшM:Q(d]+u4BU`&q^^ _^:_M_]_}_"___ `^%6N`>`N/:,-ŸMݜ^ƫQs=1P"Na^anNT*bD3j%~b V~a[>UH౤E*au]K*m$ƝP ,^=^]Qͣb@%0]3,VĈ:\iR5^6n7vR 9?`ft[gd^'tތ dRAD8QE#=,Fv%G~ЈdNNJcKnC:V%?NM+b-VRNf@ KH, 2j_`G-W4nYϰke:Ґb֠bd`= Vf|ltKpIQ/u h%⯺m~no6p g2gteLcMFޮ=g}c`9]=Zn>Xa *ctc^LFZ`vri?~ޓ~aiсV M8܅nd]j&MceijS~^~;+0Dp1xFGUiT02/1 >h0ՎBP]ȃɃ]xئmۖm 5m<`mFs.lNlf6nn4@nn)ClNolV0o!o:Hm:ׯq%;uM3׷wx;z=d7fq}k ?W$Xk~m˚Em>f6k5WˁnGqǁqFqGq}q  vkyvQYdp(? ?aEA>ۚF iE.닭d(su*v w^ xcbcc&i.r>)/*7[$]PGG%RDs7Vsezs̘s˨mQsS/SuḤtItI"bWZt5j ug=S?vH<HD AqjYtmE;sh.ƍkn7}8bgdOsaJEBT~˺v3u>jO5hnWko;%YQeR/iu_Pv>"/(C3@E~*3W kxl~vSepWq䃏cNy+.hWyHyRV z ))HzzzO"zO8{/{?{_W{_?'{z{{{Ox{Xȏ|ɗ|Vʿ'|/%(/҇y' _yzͧ}O) 2H7}{/~W{_{7~_O~{g|{/|ďN5{ǟ||}ѿ՟,H} yXp&͙b"%[I1OPIQ <7 )rd "HtLr%˖)$d&͚6o̙%Ϟ"E(ӦD 5呦V5fPJ L(ʨi,1:u.pe5+aB|z%x0a Ǝ? y2ʖ/cάy3Ξ?-z4ҦONzhȮ+h0$Ό v5߅ntq)W"xc *V>NR֯cϮVƀ/~}իA]/~uk"_~KۗDZ{4LRjHa g48oRW'W_5"-a-3X7☣6Zl V. F 1t[1dGQADrD@GTJHD𠥗"UQ}Ygx|H*sY 9{DDg ng"(|YßxƩFg!` FŦrW+p.v/"U(H؊]ѣ;⚫ګjfc?ʐEA,9)P1FJ` mF2eJj.lr% '9h r.ʮnPIꩤrʗa*krܱR2.|%%)_.l -AK.<3C t.8CDr(QGTG!pFWq Uc @GXk5[[}WkU‹ܼs}~iy _o׆]xڃ v1K8qqDye/5ء9in9[:Ja:ℿM:ڶ1r>:˟=J`n)G鄠p pĶ\M6`s-o5FK5M5 %̢%\p!f2T2\ 0Bck pv, #A$|dH}qI_#W $\*ιE'uiZr %/MÑN.O a0:* UQ|&_*fYh h34bB_QW8`oA&!-_+J-"rM|ENئ!J2PFFX%6NJPP.VLe\BňubXט[2XM#_8L&#vABmj*$d!YDbL2d jHډ}$(%K~ &9I{؋TDLQ/TqO."bX5.p ](C^^A6*L hImU69M"Ni'sf ^RKNHӑRC(}%B(` YƠMCԧ>EeGn ZiI@zDkt%I{R:q$5L(5N7]dNtUz%*QOԋ*a k4JHnLQE7xUD E*A]1g;{@ |:6@kR$VNIi]K 5QW(u.q+A1V5QD@DT&ukb7m@u7/yˋ7Ay[7 =P/}닃7I}V0yw%{[HU-kKb8: "J,B|"ŐH1?U816E#d,2h |1{9<>A l#k< 'C9Rrle#o`Z+Y'20+99n@۬9`2L.z3S84Q#sxA:Px+mKft}b/&*hLj r3W}d,βvr]m,:]޲<\`u l7ùHw}' ]@:щ^tNX4mʈ4UQԄFvl+>&Zȸ#^fc+|>79Ӗmqg[-涣 ^97uݟuhy8Mq~[Q8s?8Щ.eNzOGrԯ<įK s]4m>yn/푆E=wX6Cִ<-d=sKwso_.z=ݨ> 2%Z2@4;*ܽo~ER}X@:M4X~`ذ  6`N4Of` t B`u r>` X`~j  V`.aDF  a6-1$&1,Bџ5_e5tB_*"OhB  \)"2ܑ͜ QZ5%"-ޫ]ɠ=MU]qZ1b.ꢯ8A"ͱ%"x(22c3:3Bc3@3@5Z5bc6j6@4z7~#7c6892:c72N;Rc;c6=:#7c=YQ[ɘ,eB2b\ܹ$]i A2 DԀdHHdIdJJdK@dLLdMddN OM@OeQB@P$ %M@Q:eNRJeM>@NS+n\, d͢$i_CeYCXD^ͽ^EhAt$A^^e_^^V5''(~b╢"թ"űb?!p%}嶕%eifye0婙Xpdy_fne``abʚb"ac Y!d6]ee*iu&}&hRirgwnZ$ƛ[Xlfm^mz%oonEq2q"g)19gAgfnu (&Xzg:(ZFD'lf]`'pp}v})gjfduXv.B>rgc #yb(z.zr膺'|ޛ^c}Y~~bFtr\^b")Y(kN([RiA]ze*|*ڦ&**zށҢ"dR*C)͑g1i~'aZaiij!)jבژ()Vj.J$VY婨&Aijz jŪj@j^AXڨ+kkk뺲kzdkk++B^ k Χ ,R V@ò*\@lŮ^Brj燍Ȣ^k)A ll@ l 0m m"mҺl*m:ӾljRmbfngz,hvj؞bˈ_ &a_+_dTA@N ,Z"(.&J..4n^n6znb:.r.f nnʮή..NnZn/ XV-֒R/a!ҡma m .b"km@lF-,p+mr&r-ꪤ*Rpoʬ |*0Үp оp pC-3*u-vng]0j*q0ҫ(q  m Ӭs/z[{מmp mK1bq!#r +"p#omCpq&]jΘlq .+m!C-$;,p-2rvA)$/s0s#q%1&k2;/))2*2+ۀ+/Ϊp..{!v))sV2G%033C9l43>[@?3H?ss3BC' s::p;;'sY^&[dhhijvj$"B%"b(Fd6esy2(Jqo6hߛivttKwuu[tc6B'$B6O1{uȎ[o7T64^Xr7Yfi~v%uwx=0zgWst{SmɤmE[$IW hqWBtV y jy`R! Na B9.STyGyN9wy+bSso99 ka5&\%$CTCOǟ!/ "bc:/#7g7@и[krzD[A 8@Às+ۀ_]zx)8:ECxnngz{q΅z끌/{hmh@IX7گ:ߵ{t^x'mwkWGzw@ϛ|kTA h8m{{k XA Żǣvǃ|ջ{tzz|7WUA,<|yƻZɏA}* 8'6{c/;|د͇$<Ӹ?z->,~[==}''go5nF3+iQ7sevE?|[ϗ?׀8=>~}S~ud/~Fӝv7/Ϲb>cǾF~w#5"S[c?/Ms{?*?*m'/?X;0x Gxp('$VhUFM5dH#I4yRI+YtI2K3iʌYgNoȔHT,8E2d*ASVzkV[vlXcɖ5{-Z*d "ŢEvygO{޿ 9MZJƒ8$0k݀mL$PDE L{ujիYv F8(ݐv 9w7Ix7ʘ HWΌE\y03r9Ξ.e:zlٷw~|ή'u䭼7P+@㘋DrBj%# -l<>3ԣU\ݳ-m Q{&+U$IU.̰B%9 < QBCDܒ.T\d$4s)3KteMYU;alVJk`و?ݓP? EE-P&uQE TH]%%N7Qcԅ;T]V_EuUV]=YqUW1c\UYS [Xfi]bwv_Zi6ZU6n]6[lɽv\\=wdUGQJ7JdZEU`EUf%HQ3UXKfE|S5D3M\\a/U^] Fdm=Q8}蠅袍>G^馝~hꪧg8iڒge[gnŐ7J3q垛[CRDpQQ>W|`*7Z#?_=F҆C)A q+.e^H*#?BE?q>YyDz8}5)^S,Y,n0󁑃hJ*[,kvf} " =FOKPD@Q8$G̈́{I5Hք}j>1/nS YȲ54X0 aD x؅ 2IB2n# t %(A ~z1B <O"@b B @$FP2d sȈ&PȅUvG>8Hr&!DfBѼn--$)oHxC*P#(HY/IQDEԈ&:ճ |b#(ŏA@J$ xWP419|vxG:d rEh #$&̌a 4$OYDD:|F:+)BP\2{ &kp#$@Yb r,* ˌ򁢨n)\j$6X*JL|XD @" (s#d8HPUpQ %9tss">sb F5B;5R)Vzqsh ?_Pqt#By D="Ehҡ^E30vcG>Ԥ@))Mm? +ȏ Y) B@UGMn!*N;V-Z٪ǭyFB2*zvWJt  W+kȀ T!lHGZRiAGδ!1ܠ)*rPƌBHGnR$N *U 7\q f@ksOpQBqAfw;n#` .8lޔ#Ԏ*h0l//ЄN8<ܦܞL5P0c~  0^09xxgq&~N$0OvIM錐 0-pp$$q@`P k+G {0< '7Mpt?CQGKOSQW[_cQgk`3 .**.18M%_QeVExۑMqmG-B҄ +U05q)#!G0 q "p # r8Ґ2+a"/+w'$Œ,3̲w4$&,#fG$$`fV!03;3='rܘR0OsJOL>0MXoNDThR$RpSanSPQ,oQ ?89:'0;+E9;8:S;3<3<<;s9?%%>?s><#aҳ+0c4c<5=pdDS5T 3$ss@ LAΖ3xHxt3,#Hʸv. $nfXrwJT8OyHrR -Z'X؂!5X[9؎Yn8$8 W~f`!8*xF$YkPye$xS'.[ /xICٟĜ?Q뙞*vӣY$7bZZ!x8CnJ>Ȓ0X٠3#כңE-c$(3!?kڨ:_7b k:1=TLTM\M$6槗3Wy=z?њs=3>ӮۺӮ9;ѳZٚ=Ӱ>?3@%b@!3@90AEJdG=&Z#1MRs4g:ʦEk[6vZfz'T+/+'qƵm F0y٩teiZ%[q'1qϹ W 9z-Ǥ5RzPI i\#<Ů9K%\7 &]B'1]9~M}cPYXbhJ ߽uI=3P em)$e!pLf>aq }عU);m T]ٝA}o};} >#^#] 7?8:JA61J Uxy UcAcEAH `|A]x L |oZ= N~u>nAHAI|?1_%ԉA@Wwy`e~H=X]I] BU`-; $a#B D"Y$*c -Zp`:(d-[!g ) 8s\$Oa *Q*]ʴӧPJJիXjʵׯ`Êzг>Ǡ 1g7sM+3cÂњlXix݄ ade3fY՗ʄ09 Qs 1& e(9jڙXú4R"1ȑEǘ꨻s* Й& =("_[* VܷUD2FgT -vC!ĐC!REEĈIр#_4XJ,H2dN,PgURch8<#3PiuS[u,54PJ3^`BLz0Ř_,'sTvDuL)}6ښh|sY0rL0cm 0sGpAQg%gJN@DvtEf$1ݠHC4".E-zM„08Ifeg0)W`5-n6Y!` tJAt$anJCdEtiGa-H/4H5bO/)TEG,W b(Fl-0.l)؉ZWJ}1fS jG[-2G!ؑ 3hy0ORgr^^d1 S'"l!agX)V?n-y i*n ';٢7y"gɫ"nR G?'.GZ` [?հ!ͷ- 挔 FV/f9|Bgu"R ;h::\9ZщS% 9D%8PxS)R  P UmFd{&EH@@3a *;iƈ3(Q+`v A zi^P̀ES]^),λ:FfLZ"H' ъf Y(l; Cs 1NBDg3 u`3}B㞀H̞ED!/9>8h#::0HRx$gQa<0eBRaL]0=< qˡ%R̎y|1I}|W u] vb|m`!$1"_v v^j"`p^xw"8$8`.wj ` 4Ȁ2;6 d@j;8* Q$A P } GZ}CuZH(m1>q/ Wu>s7^u$%zuCx,1>0u@9[(z.b>ӄr _Ugх7س=/僊 ohwŁuƇx?~H$)h$!1"a\=]A x (A 80]ъ񊢲㘍Pqs?x3# #09p2P ii  `ّP Y&y('Y P.0 *Y6yI59i B钍`yEY:0By0 `  \syYyj\qY9 ɕ|!IYy٘04I@}Ks YyGiӘ)r IV]ٕ_cp9n l9YL r ٕwy{i6tؙڹٝ9)y}ٙ(iٞ홑' 9}YyY)c# `zʉ uiiӉʙڡ ڡiٟ^ JI2 Iɑ*9>J)D@ZHiGz%jy)0:XzWeyi:< I*ezh*j>ڦ٤(*Jȩ|ڧ  2p  A@p МQQ אD׀>DPiZ CЩjNP NFZ:iʫ* jڬZꬭ zκj**JZZʺ zL%Id T鐑Q r %R@j+ 2P~ڰR\Z^9bi=5 ۡ۱ k"rit*)*xpy2K1KyI#k?%9DkAzwʢU9T[?Rqɠ8 >{i`b;7YfBNƴٲ)niv{6_ a ;[{;블{+;'vP/xz{ >B-7^&9)]0^#(ݜ" P?C+N-F}4.%6. N ^W|nwLu^|JEk̕Kh.kqޠ8:Hvl\^t.&.|NznZ+ʛ+NSn6u \^25s>^pھ>^~>ln?Ȟ?_N ?_ /R.y&?@Zj U N&zjI98@:6_:_KMOP/;FJTYZ?^U??G__bk`mgc?e_jzOL{}oRUW_U߯ Z+ ˰'4 r!?\i?_a٢Nþ_ƟɿΏo$/W6?ߟ# @ DPB 6PD-xQƆs^tA%MRJ-]SL5męSN=}TPE]ɑ2 aUT^:jV]n+Xe=Ir)ʤ,W\u[mʵ#>H` hb?NƳ!G6fΝ=Zt}BdՐYf6bٳ;~,sf}\μמl[ymb7qݝvݽn|)Oҫ_5~jzQ%;=V+_ξ~I

tOgy^'U x@N¥1ЅyZ_І7ԋ VЇ?b8D"<4bD&*MbhD$" # D5d/!  fSLK qH@6qt\GD42vp8HB2@GF6d$HIVH$A (A@]WZ4I 5G*4 }*<o2В! )HCӘXHәl3)hNӚKkFcMF sDf:չ?e L!g<8Nys$g=xF4IuԠk P6ԡCQVԢD1Qv$hHE*ё=hJU*Ԥ/I@Szt5EoSBt=@ R?BOTf2թ USU:UPjTvH-)VֱeYJSZݪW:Wn}j[ZRU}MQƕE] .c!kQNka Xv(ŬeZB5%i͙ڱj6%ml?[d&E-LY۵얷wUk#[&ZZװ5/lc{- 6}J;BW%.ue]-^Unzݛ䵯|]WoJ,^^5^!<`"ؼ0{gxA]w`r 4km;cX2quc?1~q B\(Oʠr[e-wy_3J=9zEEgYЍF2wlhJH1mcMoƝCJoA ȫvcNS) !,B  !!142 + ++-6 9 9 9 ; !@!!@"$A22K33L33L33L33L33L33L33L33L33L33L33L34M56M89O;;Q<oCZqGPbGN`EM_FL^FL^FK^GI[HKVKMLSTMWWN\ZP[YUZYXXXYXXYYY[[[k^\q[[qYYqYXqYPqZIq`IkoI\qIZqIYrIZrIZsJ[wNW{PTQREHB>C9Pl#Jo_.jܘqGAQH'E\ycI#SDѾo⴩O JTh;4$Ο;y|ԦԨV ׯ`ÊK,LMl0@-6#d\g}&Gs LÈ ?8bEK1.%XcH˖QjSF9뼪x̩^Zѳ#I'ҷVmZ*e NV!L,ʾnU[ܺw:gScNӫ_~/qcƏc/M[f$uvgbVRFnãFa< S.%,>qMa,((ƵUlkg8x|}> $|'Q~Xg ԀOגPFaoXvԅ%#GΦ+Ib%h"+H&p)M_*_: C_i$DH&"夀N>_fJy%i:)^ZFWbm)z:\('| ꧯ&d54iJJe1`O[Q;jd@fjb "o]њjg"z.og.ڞpi\VlsyeǏ|`R|a˦Wcɫl`d00{9Gp^3JdD|flX{rq$F[ծ06Y2[pǝNG P4zpݝނw|mԍQ=v֐G~aá]k=jܤ=^CQ:>gbK 砄k>0^uҶ~v_;EC|pV;o?Tzτ8ډ=@3;^=@pqB_8׾^- GHЄ W0 gHP?u D B7A 0bA,C XAzo d\Qvbȗ71H<6vPnHNjm>!hQ y (ҙ!h^${,Ȉ/l{%?`IMT.yFǤmuL*tUX+sā챇B Hu"'ЌnBe/,Pf|JS,tc+Ir H:w(tpꌧ . @0?#d.9xF!&6ףЅgΊFnͨF/Jy*l;=Nz*@IQ&*  d 0DS ICQՀ<L0ѦVNT9h+ԝ"(IPP!rxR;IiJv B:TwE#꘥r ZAMb:d'KYvtj>i~s OzNdjUWlg;[ᶸͭnw[oWbIP K\PЍ.2K]::;g{md8C< ͮk_Kv/n+w+r*]j ]TNpKd\Ȑ!ָ0pkhCրo-a 1oQߤA1X@2,eDPld%wH1-hqWþP2NYź2[WM#9H׬XrhLg9s#-c!$%#A %dUreGCzҒ~t[MO7sqAհgMZָεw^׶, hA+Ntӯ@F= bٗm-k[ʠ|R+.Q'8e x"xR=W~MN60lޗۻ;nC|⸅v6n;!ވ42rXNי gN<أ:w[ {_elY⹅18[oN4oWp򋔜&wǎ[-O;apo='Ƒ;ٺ}/edP'=:E:;zݨU=g Xsg20#Ö=utPƇA_' `X>Ay_y/_*wVv^nOW--.G]ݽp56_s1Dn}}}''~y`&o7o74oW&8{NtUYQBDDiUo2S:X[<؃…VwnwhGg55vdׁg~k''Zv)؂;Q-P,Z S%Z.@= h1Duwׄ㗄ׄf'Woigog;[o]Oﴂ[UӐ\Z%SGǔL+ ^4Wj`lphHB1u}wyXz1vUj҈xօ ł[eR?hO%S\HUV8PdX]TMyEWu8}f~81!O$,uRŔQD+S4RTP5V蘎8M=Ž\⎛ff>zxXZHPPYV 0UR;)%4JPT s4 I @TN #%}W(]9#yo(h{~Y&E@(`Zϔ~'S>I]4K5Yoz3L@|{4VXZj >!dgׅVE^pSVPS3X?I D=Si=@=QC8׳E:דȒk][9G_9&xyWC@ >y:d 0ϙ8ٚ7$\Y,męgٞل:oI MPÝi8Y ɘ{K {`ʹEɟqAv@7jȡi5YK *ʅ9.1F9]$F 0ZXTFA #F(uZ+ڤ5נ/YruIIۓ#0yTI$DItPJJ)noPTxiG#hjԦorN Vxzr{zyIQY:ŤzFz戧y9Q zڪ:Zڪs?5XH-5v k aU,CAYOr P M: ZVaHRy8$UZe*:{Z*OWHRHhE4HxZ>ЄO7RI5ZZI 90Qe0{aJ kغYկ<]pP p$ֈ ]fePDϔH4IkY:j;< CE{TZ7·H[KM8RISDV L=9SSOݘOp9z8K[o;u Nk| k^ӐekIgR0~LZ@۹FdOc+;;׋;ڬK 1kKlf^{N]+?[QG۹+>۹;8۝9y۽aPkljq^[KlSӁRГTD*<ܡ?, \ċF+LPR|NKT< :…QH4^4[`$iLh,mEGƫwUxyVL J\\^ŁLMdj> tCɒ=su|KŠYlLN<"Q<<@uAҰlxlmH7{A%,A6!CS3p FV*I?Gl EJq0 B\12<!O,0ڊ)f.R];>ҋ:&MNkʢ-,P 0>U'AsF26⎍}.m -l+WP~ZV+) 7.~nnpr']M|/6-ݢ-_; qS|B뙽<ÑylQP95N^XXg<y!铘~JQn.O"nGzy tZ&yr$\Q& _~<&F k@kө _5JE 0ZrKȄz{9XKTOzZ\OX[Me}k :Decߧ.OJ"/p!N{8yݣ ;o}eKZ?]y7FO Oj*xUo/U髯i.McqL?W#T~8}M4ޯ9mCO@ 4B >1^t+*h1YX!H%MDRJ-=OL5męSN4#TG>!cH>5RGxUMҩN]ڔkX]v% Qʵc|RK }81^_"KW`… WfOƍ7Y^@RٯS3[VreS;c֌4gߴ覠l=w'\8q֝\r+oX齭_/jV۽C{㹡}>@0'd߷b@u/cAs+ڇ d  %COD.:EAqШ -p=S1Ŗb!H#D2I%\Ȃ2J) pJr)Fn<4y2͖TM7[xlIo̱։5Ŕӎ:kXjJ*']!;qN*'©gљeXv僬!^خpOpv_/i&xk?=njEk&FTlBQ{ pf탘߹h ya^'z駧zo(_{>|.-Tg}7n(mg՟]EG:'$hD4 FHE4FRBbfGccwCFfo #H;wF=\[0y/"_^iNfCz 3:213!gPA_FyQFPՌ7E:|kH]$'IIgw#B?O] %xRL $ Y(=P1&tDDTT 45ZďѢ%-HJ$4}I`.RP tJYҴ;7fT {ٲ4 .D}Qas ]SЬr֪P(D UΎ6sDpr4`hXiDuI,[mhF@u|'Hz< [7) X|(ThVDG[R5}iM{bԵ-Gk_ֿ-&"~2܅q+".$my9{V/3E:DtYD# no]G B92y=B }r7y5Լf:G}|UXSŪIk: t< Ф"qmWTtsC'4@jf~Xjh$% )c]fA v d+[n<ƽ-0c z"}v?Dwմfht7ь&5mGy#.2!+5uJ>tK|&Xts?omn%FDr MzJNtywD >7g][{-W;pC{9d|k'"vkOzŽҚ5gyWQ{./oyUx`f|M=v#S<֟ җG}Uzַ=a{g{~Y{.ؑχ~ACoż!W}j?CMu00|տ~7}JП{܏>6??Td(;@ Y< ? @DԐ TAcA@|!$BAp"d&|9{1As*C99IB+/\(D243D4T5d6t789:;<=>?@AT:j@DTE`GHIJJtKMNLPQDP$SDST5`VtWXYZ[DSL^K|_aD^$c`DeUghilE]|ixDcdmDldDeplrDo\Dp4u\DtTDgxyiEkdDwLDmdGsGDT~dlD4H H,GGztȇF{E|lGrDiȌƌ$HoLDȍFE,IDI4|HTMDȖȗȋLəDtElE ʌEȣ,{|v}|ǒtǏIIEIDIʮJHdJJ$I}Jʱt˳Ĵ\Dx4JVǺ\K$J˴|EDI4Kl˩s|ĵ,˶KL̷|ǧl̖,ȔĚLôLLʜˠ|ŻͼLJ4͉\ʓdDPJLLG|Jtܬ͸$M LJaPM`&F`FVFav^``顇9ab~!6#Vb-%vb()ya 'm.M0/&c2a*V5b+tx 3&c3.;>b;c'Nc6@`7raj>~b>V#FaFd!cAK~`>d-[ 0_T&eSveU>TUfXY~e h_\.e]^_eXfc-b\ffagcn_fYflfVmmoNpfp^_o.gqfXfjF\fg6nvy~gbqvR΅g~.&gv`h^x膶h@}GdvaFi>NJfchrH_-i6VV`k؇8D.&6࠶`F꯾ `x`އmi9b~ankjj5ƀm؇k꿶lF k_.l6l.ɶl1N:_mц՞`^kmmfni؎mlli6~&̦nnl~lnĶn>o^lkVo^c{ Gunq>gfr^unpu p|fpzp[tpq Gfqf?q {VqOfGxfqxq N6h6rNh"$w~#ʖoor+rpݞrorm.?/3Wf7f8/Vfs1sv9=s־1k>'s?st4=wt9OtEO>sBwkC/tDtvsKgkLtMsPjQuR/^tnt9t7OWgXs[3gUuSiTuZ7v[vԞbtWOvL&pQp^fvgqmvv_qpo7mwnqn'wtrwvslpxqyzvVn'h>%GrWf>&r)?uN^sU[v)^_jjjjfus?N?JygyHyK-/zy*yyoz0zz_@ (.RF0-&^{aToT`..`§`?(B 6PoRsF 83(-"(.){4Fx`38 T(ڟ.@|`o`3h@1?N |%{)؀_)(_ri؏S0 >4} (x(^ 3?|<4_4R` 7p1e Rh"ƌ/>#Ȑ"GVT@$ʔ*v\%L ZDH):w4u<[pB3E& M3)dЬZj}+ذbǒjlla`KZ M@c75W݊ 6@E b-Q*˶Z!}6r˕3; #帡-wYiGsl3ة= {k̬so޽zmݸ?C{Aqy.6tg֌a*gy2¦^;@h;FO!?-lsF.f?Z#T *G&q I]DE MtD1iXzF&8",x"}W<]ŅR2GSXM~{- 9d ydXJ`GZMQ!E[zR_yRc HCEacGiNfB6rPG$]!9hY!uyF^Oz`AbؙVzGebAr)u-USBZ|iU!zN*JܯzI{*,W{ӊ9[V6-r+疈#n;j.'-,+3(|0'LD#E2I f)gh"d^`(iAŹ&1tF|,#k6gUpUTDE` A\@qC}co$2!JɴF ` EtZf[vE6ۉ3붑ptFh7EETC{ksuN8\1NY74^7э 4)\rK7f&qr|%j1OkO_e=m{=㫖/o>€i wܿN?hWȀE:]gEj’,Գω"AFW `NPCU*i0p;\ &BЄ'tY`&EQ-#Ml0eO<El;"H&nXe,ƒcFQ&u4!E7 qZ8ND#ǰqaID&2eK#HNrQ9(YI]+B_bF3Fĭ"8jv| B*PUNu?NkAj QZVΦ9N? i@o 4KyѤVpBHEғgl]ORH֌DwdN X!F`J%+Us,:{23s;\)"C4jgQa;( WVdRNi'\,f@E*]#!EnrZ+!-ˆ0l[i]mNҾ,{WE~ѽq`-jWfU6E<,1gP.A -,o5a$.N,8z匦E jŮLKr^ģj0WL1MdGyiGs9`W^%f~9SOԸF90s.BY^y`MhIJ{dk^Jsvߣ3hEUCckƪZlbQ-Wo_+^vqlc;Ld:[ֶ͟s0hë.! FW#ub$vELa%FwFsQv ^2Wķ/onV7F5xJɁS.s8CG&?9O񔳼./9_.\`9s>9Ѓ.'+9ғn+Nџ.3}VÓ4b^:| 8E^Nq x|8ƥ)XZ]{;MY/"69x 9<Į͓=+7&>T @LL@7suY*ps5(HCν CpsO_;%7pShEĩpW^Uo¿}STei >=Ba^Ո\)i\q@L읟Y\m ɟ]`vLץ\v\ ` .<\e\Mrp)E)_V՝ !qaZlaaa/NɁ!Vaʡa~!!ڡ!a!v &b F"N\#!%J%"&&a\^0 Z \b,zAZ"-,bZ/>,zęBu\^\QA# rNԁ5R]6~ ( _ΙM0 :\1vʡP}\3Z\HAE=FV666c@"y7*b΍cQ_**#E#Ǚ*"ed^iU\卤dA\@̡$Km"0$)0-~c8 b`*$EVĕKJKeʭ$RQ.iTޠESUb]V"]SnM%X%T)WnYʜY;%L%\#Y^ZeY%%^^%`bD9@\eۺbSa.b.¡[dbd.`ecfJgvJf Xj[iTegQhh[OTQEZeUU}r\ZpB}1Ug ZAo"t>DZ-x'cdĦ#µ%j'kg$lg缵[~~h(a(f§&1(l曀nQmmf&gQh2f!h(h|((Z&^Q^h*|VhiJfžtvfgt^oRn:qnnJ'vri&6iϛ&Ei'yaP'"]h&6ڋʨb[)jĒf*mU}62z꥾jQ.ѣJ꠶*ƪvj멆 k:gimfuخΪժQkŨ^f#+Y))rgXVm'pkkpkY)'2'k~ƩzixFlB^lYjr볡k¦6ѹ~,f.Q*vl̲˾gʊnɒ9frrJk'g:j2l檣N-Vvʨ*vmVʪNlbWuDPhE B)-H޲ʫ$:zЭ&fu\B OG1Fm"O6R^&llo.k+~iꄚ nvUw|_y0EO|'>6fl.dZ ㎇oFVdPNoGk g*gOp .>h kp Ci֮Ω춮nJqnnio'wgBVŞqbAĶ1000ih/01,!W p"q##P$1[ &N'C'Sr(K(or*2 2)W0"_2f2- 3's.21B锦nv1/qR1Rsgtxr6[7O.U3'sz3c102r>*N+$t,O,r- -22(t}p'4A/CsCR?3?CF#4*t";>tF{QGGktJR%=G0E3IJ+MK{Q@H_tI0K~:!A@RR75N-@54WBMnnq:3VVu5#l0WXwV63W3Yi3,=:qs='F4OCr)crMk2`RNNô`r.2!Ote0aOa_2#C4$K4/s6utC'T[4U6lǶlö̶m6nn6o6o׶op7q 7p7r'rq/s?s+L7uWu_7vgvo7wwww% ws֝t7zxvy{Czv{7}'w|7~w7wuq3,wlmB"m v rwk+8wCmK8[87hʶ7FlkWxb\}߸t8k*w)lo]Clw3A88,7;x_9<#qvP96yw9v9z9su0AzlylGz9[LJ+_0:l;:lCgO:3A/glsk{:㹨Ǻz :zo3l 6om#x°v{b$6pۀsO@K;G#{k;^r;c> ! k:G϶6k{l/|6|CJ<[;vv3Gȗ|ŧ~{>nlsA=>>>;9ȿ~>vN;u>>k ?>o>ӷ> vm99bT9}OkMJlo]?'>?@,2"LPAe 3ETaņ]hR \X9dI'O*@eK/aT fM7qԹgO?}>:hQGdS1FISfZU_ ;˙@ѦUmͤo]Zn]\%Wo_|ږpaj&Vh`R?Lur喑-gnxpgϞNXsiMOFjւ9=;hH!ֽ޿>xqǃFys̕?>:ձgǮEw?|yџu;~秏_ P ,;tϿP P D=/ q E/< _p=W\Quܱa RHeFSD&r) lʖ^ 0ز/s-.SL.,@̳==9T4;EIS7TNtGIASH=EP[S!4sV\u\^y W.sU^=Zb|H(s'/\ ) mq}Byy<^*]l]zG8r-X|Wv9D81#77(@cO6.ޔu[eZEHBe\f9w>`(裑6Zn駡Zꩥ^꫱Zk.lI[nV[zl\l / Opolݎ\1vs\tG/}MOi'o}a]v7'sqsߝw}t]w}߻vn=Ɖ/~ユ'zޭ_<}ܳ>r}ykm?_?o_G@̀k@% ,1HAjz '?Ђz #B 'T =hB YB氂.c 7 CЈI<qkǐ"8UrpEH/brH=q#ҡOfcφ9x#G=:Z! 5D&k|l!Y@[d%vILFM<#IQmW'WRHce+Jɍs+` \rdZe Wrl_01LbOɬ5MCjt|4iơfٱtngAs8BrfYgvh|<)4|>3mCQ!4![q$ k((Zщ]Tee#^_ŨIOƪvivMmo-n ݄Gĭhy$Lmn˞+"71Z9KFۭNw%)rPR oj]H9(EAJQ h a@!#QЍG2gSBtS)P S@n.0)A70[^w͝[ @JGfPo"@Tj\ x_@dC9"6XxūYƗA8pL!E87*1p L7>NsfY7;MP7"X)̀ f mÇ޶)5SF^;Wiz@^ϼsv'3GsG҇r*fYVx |.laTyw~cϘ#}.=U߰/l=Xz~з|z _^}xOQgj>?.@gpi[!'7o2JEa뿪vbtj&c-M"Po-370pͮ mކJ{a((edpimop*y}0BwO0 py` 0 p 0 p G 0 p 0 p 0p P0qgFq0@ if14=q)/-151 {msAMP?Cmeq0Ճq1C}Pqyǃ1C7Gq1oDc1ѱ]2R(^Oq R [!_!)"1/2#9r7#A?2$IG$QRO2%YrW%a`&i&m&q2'ur'y'}'c2('((&r)1))(r*1** r+0+++R,'2- @B-2.r(&.r...//2000R/gR- 221) %2253 13r3AS =s424Ms I354YU55ea6wr6q17y_7~38ӟ8 w3989 # %2!!R ! !S;<93==ճ=e83>>>>3?=??>4@s@ = @3A<AA!9 r<2߰BBCSCACcpBTBI8MD3EU7YEsEat6etF[Fm4q4GCsGyt3}G+3H1HHt,9IGBIJsD4:tKKt*4LrL4(La2MՔ%ٴMQM$tNAN#4O1rO"OJIPPsJJ u"F3RM3P)'R4S5uH9SSA5IEuTTM+-5UqUY]U1VeiVQQCP4QWQCq5TٱXV#QuY[RY5ZEY$Zu1[$[;[Uu\U[͵"5]+QWJu!CIM5_q]_1` ` `vaa6!6bpb)V-b0c5v 9cӰ^5CdG6J 5^du_YvV]em5fe6Wifcq uvgg} msho2h'"3 A(&LAig Ra& j2ls `&נؠ3iR P!Ԡ& R! hRogom  fRppmR  nk 2V6 k stg qtwpk@m7oiW2/7kkirs/VtGt xwxmukaw&vkgrsoWo!&ݠnVxpwu[ &y9y210w11wsw(nor Jwp7uowu9Wv}iu-QrjpktQpwkr `&r (w ՀkA JsM W(RV(Ttilq׶m+rCy2ixihxhxf5mv8go)hX7E^Q֌EWUQ Rg0x踎08axa9%v b 16yc=!9d߮3&yu=ddUWEYHQvTy]9%9vi auyYy b?dϸ=ٙ9XA9ͱyYs9Ecy1Ӗ)1!;b !L<# :' !,~&l !22!911111RrIZ,%,B"9S @bn7)Zz3gr6;;Xz68B81Q&Ga#V^*hs-|Wf|6!IhAa )8M@<%)%'09,So.Yut~.IU7<5;('_>r}.0j)*@\@17s=*,2qM5hP= 50?z[>;64 "HR75l-a>R9/&@j<30:zA`K=iu7e?3c:Q\7Xd74p@)*:*28q>\89?' !|.+,75JR*j@x@hG3 '7<'$[\d{U61u@zT6|V7 $BH)G1.01E#$/H*\Ȱ!!:Hŋ#JȱGj ɋ$ ̨qJ 递N_im)}Qީ"('Ꜧ'/Z'y|^kT~ǫiRJl0R e@g-%17X1x#ld.yd W.3^1bdjzny4^W PCl(,>L4͐0Dg0xjDú-hdx, 3q8Cùu$xʧxFK1xicLV75A=x `~@⭐x5=\,DU^%W y < vz꠮:~kJ{{{|jaéj.b`^=ק#} Z~ۚjwfKrN=?T-5IRlTC5P T4=y;V=iȳw;≧hBT&vɅ[* c8̎u"D @ tvP9$d)`HBrv(9('8s* @NG-j^tC@Q@A @N!H;fE DɱרE 2P!?JG $Уr*y1"L'A)C29 XF\ReDx)LS%2 Lr9D5iS9^dq 9 ("N@!!"r f4!"nD?x␋a5 !~ "B#JQL Q/pRHQ^PGeP.`_oO)l<.ɧ"JnlQτ@·Xrj@Ƨ}Q\,Ul Wӑ@ YKn57*Q*UǼvQ_ A^4BH ak]8[y%^0 xᡥaO2bdK327iq,Xb-"G 8^xK5]:yɃEjb-cmkY(xaoXⱑr#0aϱǝaQ l=:cݪ=G~s`XàLd]2ы~tt]^N5D>RC5U]WҮ^ i´O^z׿.ϡ4a z&Pul9/[@NVAjBO+p YcO5mV鹛jMzη.oO8 ; 'wS' H`8;;%ONr|.E6Wy=E?;[=NӺ#n_}RGZ ` ;e۳\F| @O3[G<'g=7΋FJԻ =%ڋ?Fx)_t@#/?">DC_֟>/P?=mO^/~{~W|Oۿ?w~hx_sw\wsk*Mmxv߆v8UvGv c`B#B%B'B)C!h`+Q/Q-XgRv8Vmv|6vW5wfy&ՁAH$9X]hjlDŽPb;%FfƆ憥&Ffvm68m7hvNbSTxhmXnTlUnlx0'X|hԳS\wԆ`NJb׈qQULՋĸĘQQLpLL˴K،tӘLθ̄xQX uhP긎ԎHwd" 榏 ȏV[]_hx$&((*H,f02^ǑE{׉Iȅ&Yփ(-- #ȐfQH"Wcȓ.(+v)9I)ᔽH(4hGI(Sȕe(gHiLykؔ8`o`TIfuنwy{9}Yy`Ɉ)J'G6Pyivsɉy i9yh(ɘ*ikeUH,9)hw &֛hx5ii͙>Q =ɝ 􈌿P>44N@DJ4I!BOqE!w6)N`U8JAFf01FnGrT" ""\Q\TFJ~H/; *aP;>jJ$!PD NDKJjK2JTpdHŊ3Fȱ#G xIaH%SuѨ"ŪɮSY]*vկfLK%[:yX@W[R&t/_y LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞Ms͛7-nƓ#O^|94 ؀t֧۹{~Ax`ٟ?y|'_wםf `YA1 @`afN(Xf  T0,80@tF@0Z hPA!,~322!44M11R)*@ *%1, ==S/*8~ox&;;Xpcmt}=6Cq| * ,&&2~#&'1 klq*&267AH*\ȰÇ#*LE PQcŋ%Iɓ(Er\r˗/Y\ 怙8m)SN}TK#\S)џUcZuѮ-KVٳhӪ]˶۷p][\ؽA(H/^Dzu8d+YΞ&HnqׯU P5l~1߿wxAƺ&> 3  ɻ0b* QB_1b!,' 2244M*%1~ox )*@& /*8pcm'#67A *&2@A LP‚>QŠ!bLXPF:]B@bFEgGFhIFeLG`PG]PE[QCZNB[F?^<f<>>AG>CJ>DL9EM4GN7MO;OT?V]B]fBbk;gp3mt*sz%x)y2{9x@s|HqvTwkbbl^qZwZrZnZnZn^qjxlzjy_sbkkdQC9:=>@>>>???@@@@Qa|ۚЫոɼĺ|[TSQNJEB?:7689v7q6m5x5e3?121111111111111111 H*\ȰÇ#JHŋ3jȱǏ CiQ<(OLr˖0_ʌIs͚8o$ɳϟ@ JѣH*]ɦPFJիXjʵׯ`ÊKٳhӪtmжnʝKݻx˷߿ w0†+^̸ǐ#KLcVϠCMӨSsYװc˞M۸񶾼; N{WVУKNm擱[νu/<󫽫_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|;߀<n'7~8G.Kygows^'.z꬟zz촏>{G~{+{{ħ1|Q*64|9! gB0*$ /% eC 0 C E QJD#ql% Y Bi0Xy1_dah%q#x<‘kc#r4c";:$&iG |$ WGi҅$}?02='Q9ҍte8-);JZRc#XLXstd+1⍐ "58e.ӒL!4Lgq6o9ff&6Dd.)]s diYg*Cvj|g@WO+"+yxQu( ;zQ1SYQJԕ  SN}3mJLӐ|"L Z^ze(2Ѧ*UL%;i4D{JUJ7Z8Movd]A ֆ5Y=IMjjգ;"Zmꕘ|E>(֯U]k_Iգ&թcUL*\*W|26=SniQ;UӮU!czɂRm{Z pgakXoe)Z$mkܕ֓uѡQek_XJ:q\ o|9ї}_ͯWL%p`[$93,] D8#Fq?UHu Ƭ1[,>Ʊ|a sț\.edMe#WM[ǘ\֭|1{@޲f5giu g:vs>πMBЂpF;хV4'MJ':7hIsӠCMOԨSNcoiYЫ]׾5a>6lR3٠[9[m3m*>rC~?rK=.sG݋|3~y_үz#}_?~OX_}?~w{~Et@ @ p ȀGHFg Ȁ(( {&H#!,E.(x0H7hx:;%60(-?x=IxE8P8JHThR#؄U[OȄ`5xZH](af؆2lN^kx8(oh|(zȃcpHxgh؇(ȅXn{8GgÇNJ犵''WaGNj狦'GXG @ Eg ʨ{uHyyzH۸uٸx{݈wuxxh{xȎ8u8x{|u|}tXy}}  {א=Hh|ّvs'rGI&})-+9* kgj7P o^{0 0 0I)䈒Pɓ TIPt r`HwpL7 R`t { ɓvQwE Pt_vG7@tUPPUPtR0Z~Etyz|5ɔu~i\YtPoL$Pt$ )mt7P P9yٜٔIuS Yto`{FGG9E0t8XXy9hȉ9؉ VȠ)I::zDX Jʡ{yΙ *: ڗGwitYity$ *k96LәG|ɝK'ȄE'Lu0,jiz7X\Kk+mKoq sb{|t^ɚ_JЪi `t˳|мaK۵pg|F0*UX>JЯ<|q <q`Ɍ,_T{{ ڌ  FPLY|ƫ_0 Y\,ʽw u4M@ͣ|ą }3ZH78ͣ>zy,mƞٸ*.,qtU:<}u4MքZZKF\I<@|zER|t 0<ז9ʪyی/u@0KgaS t| OK|t l=])ٜ{{&mu<khmZ-ʝҋ݉%|MݹkH[ݗ]} ݿMʷ{_@Ti ^uc ٣ËނyZmo_P }-0l=E-z<ۉEdzƯZg=1ފCEN̝0 6P{㮉M=̠L+g-] L,f팶T ,ί=9)7{zN*~nl||.{N΋e~h9.LG7Bj -{`GR0tn-GŸn6Nm^ u;yZ~}>~>^>_nO. u ~( 8VyX) %Yv_ɿ0O ?՞ ~[ٕ7[t<`=_݇]%<l<܇ YYȌy2L1ϗ,N.<(>*RI+,&Ŗ KO䯙 LGVt/]|\wHݬhArRvqloͮ)ott<Յy_4u>ɇx'`Lރ=ڽ lՒʤ<9PPGߣ +oϿ@I]oˉN?u?,GA  (w 6\Æ 1DCBy)UZx2J(iM| ˕1w=ZԡPD{6d5iӜ cB$.q҅rP#G@$i)S9ΝjԻUӺ<0R‚ q+ԫ7lشHQJ{/,l>Ɲl~)ӆ޵Kp߮?u\l[{=; O~]\ԃ/7_\@9LtI˞~ktQ#@D0dA$bh@C0DG<DOD1ī:shʯ /C&@wtD5$DŽ{ɇ05l( l0栫ȋJh(I&jh99;63E ӊgty3:PB E=b=$S? t!epHL7SO#lˆ@3T? 4AӇH!彆lalPL8ԲdF֦@M4!;nѺkّ(zYj{Wi-_ шUV4=[qWC|7=w_9AXad!BJ'^)Hޘa;-F4b8ݎx?Vd:56DSqe}㘁tyfk9Ӝ[yG{.h"ԢG$h1i|A骣~ꣵ.kޙͶP~nNn&~mpEיqq<Zieq-Jn[K:oX<]}1=uGH@^|"AK =!k1QNt9aOC=)M #PAy2v¢p}ly\JrІ>1"dh(L"]HDO"E$q2K"tqatHFPe"3nь,# cWj9w RCAL$3HJ^Z%3ؐC.(lH jr~4*=v=Z٪! OYb"Rښ ]¯d0gyA} 3~)1Ȭ4OARz5L`sf q2+Ωt4['b0Oz֓g>O~ӟh@OԠEA P6ԡ]C%:QF(D3QӢF?RNt%EEORt-?_Ss5i nS0)"ӔC%jI}T7P(F{25KjH I|eh?IS:쉅o#6}+OWmց^yִ֓jNcІLlg_Z s$V|X&:s |h$ FaTzB9WϚ@g$0O5X"Dj( 6 Q ġ&uuPn U<ۀ򡴑Mry>΅] J]l$Kꓽ1po ԁXuėk#&^S…g[▶BCRxy*̓ Ս9bςxc{Ä'qXdX/=!)V/=s/B%72Si;,͊nc:3GI C==,/wOsFyh{.Ug^9g>G1sbBǸT,(Ǡҗt9yb!9Nf>t yo:Գ=_R?> =iWefts07 S ғ||*_(1‡S?UMqJq}+:z/֭lϳ fS vFxsz/uL|PS􇦮7\[@@KJ7W>k{}oEd`~Rb\MqDb'*>LL.2x?S8S<;+Ȅ후>/{@֢@JA0c,ǂy, <3裧>h,㧷H<&[Bk{B)FzBB CC+C3<KC5\CkC~B&|C8C8t?>:#D@C$DkeDnjDp"&k'y' E$HZI orEJEqD)EѠb+VWD`XDYdȁ0ALDbd'cZSt\  @a jDo<pTidqDrstZ1b7j=#;:jzԣ{\#|#}l#>Gl6;G-bȅ HyLHt!l",H!HO Г0a} )̈́Ux㏨ގu]^-`B 0N] ͠9:v*e  01qSI!?`MeOYN?N%V^BIdS9MVO>VyӉj鞁k\ơUN^驶-vWDVz%o k{#W.b-bNa"kq⯾Ia5>kֿkV5Ej|h}hl>ƾxll.j^hiYJmFȎҞ^hnmݦh^UYvT.:n 2h<~Ն.^fnnQnn}n$&e6[|%m.ndd6fnmVjilonnUk\&j~o o 7Np~1pjk.lkuk4vvOlDq®qÎI vk.^׮~qņkkz-'pnFn+Wn?o-ߟ)wr.r1rU3lGomi9m4n:sp8p>g"os&sBmAoE/B_tGottAUdbtJC_K@tNM9?:/u&LUOpQ^ZQ@¤0uiuLylpj~s Tyu$!׍mnshsrcd&'r wqW$gwۈݘw_rxvw~xFWrGlG&gz/rltK\?s_hn@pfvfGo!WUe'm'hh_?vpyb''P'vhilguwy?oz@Oza7"uǘ$7GWgw{/{'{GWWG|w|ȗ'|ʷ̗{_{{JIOO'}|W}o}ؗ}{ԷO|bX_b E $G@{to@wo??JG~#oprO}~ $,h „ hD8ex7oB֭7' WlL-x1ƎA$iJB*lH(ҤJ])ԁMR*(IIQ&Nx| 9R g.QL@AMbQ$~h&_ΪS7ItVwʛ ,v1cFLLBt&p^XNsfM)}꘮.Zk׽WatlU/[ɾimNGmukH[[Qqۅ'lkjyӻ}7o| ?8 N3.K<8!s|ߎ@!򑓼#E,RwcNq˼sp<89D'яsFX&WrϜVwүQވ)~oS=d򴫽loP;(F!uϽr#b]7 Q ~ o}yC~;|+y?!;CXl^w/^{XO=5{ⲯo_{Ϟ~걏=Q; aG7?‡p* W$/___* 2N` FM > r&`6 z b` V` ֠  `  ua>a!B6!. Z"a9](!B|B(+jN^)!&a!! "J"f`!b*"$[1$9&n^'b'Z^(^~$|_e&!$!&"b"cu^\B*>9Y% A i#qcyځ՝)&c3V6^^;28]\>A"B]C[B]D"Cz?@dU$yCdMБdЙ$Сdϩd1]9c#Ad3ddHN$KO\P\#$F!2~:AHƕ-[ VTV%D !e(lVZTTU%UT<0WZOeb%Cbۤ%*ȃb¥\e}U_""`6\V[f6e>ə(*$bf\YgZղy:Uf.jFlF1i&\2&&e)$BY9_jrfsn.bfc.gV;1X؍YwؐXxXEWyYzW}zR|}^W|z'X~z~ف(⧄-g'2htN1\b烲'ʧ׀FMh(|ݙ:QVl2ump;&cFoVsk|1CrfڎԓFsSo\.&X6Y_i%^jiE骑>f锎%ڛڍf=)\.iihڜz:XTڠ2[ڢBRF&a*ir*y萎NөҕvF쁢o.[].)ʪj@F@$|9]@$:]%@;M8|$uô9T:Mh:h9X:kr6*N@$؁L@BJkJ70Qde1rꪟk򪜞VkS\kp\@Ț L9e:k,캒jD,ƺ9pǂ9k@" T*lÞL;A8þAźS:9@:m:}:Umަ*fѶ٦ڪp glV jYѺߪ8"ۦ:ʦS .YRn>M@%pz.ܒ@.Oa9q'h (~hY'v"hNh^o2&/Bo/hF2+f.FD:IȚ(@-:.l:'/6/F/(;nDD\Bk@B.::<W0(PY-S [*`S:vkܺ,:MZ810YrSײ/0 ;.bm’NB9فLB- +L̞JZ  ǮNŞTBж$@l1ȎH:Qqi:/w2䊲 OeB&@9Aq@ /Cev:jr/ﭯ?ՁLX#SC@1 /Q:8 3Q1rƓpW}++q"O>n֒j36K;<s5;Y36d"Dvf3dOSvI[ve'#dtjrllOtm6Rie_sh+hWqoo?VqGqVpgpkvroswVtA6jlc7mkms7nnu7ovNtVzzvsvF)ee0X¶<&F 8pri)xj18ZS*6+Iܱ@z.빆?JBŞTBþqs8kV7tKKxy#w+wYrwtǷ.aiqZCwKwaqu?y[wwJg#?@8`A&TaÅF8bE-fԸq! !]cI&ITRbJ/at˙5q̹SM?':PG(FD":rjUQŚV<~VlMecE[Ȃ@V[,̵{7/]x w/E7Ą/V!?ܸp1o>LyrФ-P=m5ȭGZ3ՃU=;kޠw][oᗃc|E37ze͡Ö^&>zx쥗']=ǻ}}gG O>;@()Rž.D KڐÍ<0EB&r"w:Ɔd!‘u<  ԈR>Eİɓ,2!1Kl1LD1M12N 0ϵDϲtN;P*QͪQ*R@R&R7(S ;M1Ԉ>)SQMUyTmWaUYiUVmU]y^ VX[c-e%gUh}vj=l]vnpɕurmtٍav]w:"w |ݗgeEV_ᆡ}f%SN& 'UdWuP abwgN⚇uH8:`݂L8ՔWF pIZaAaiƙܛyl_H8U K>H-(V9(NSՑbˮg59ԶDnWxGFV5S>Uؼ᫧zAN=qiO]]uU]=6(qu{>l:v*NV9vٳFw߃^Uuwbn85bW${q_W>eGuYg?uV騼#i;>e^UcBU3nhKpn^*@a5A t1 A,8c/KxAT.&da]L(B3a S[Đf:t wC0@(>TbA'N1LaWD,"]lb{E1ЊP#H,zcbhF5s4q4_ JS~O?MzeaXG>Qx>f򑘼%;)>J'yOQ+yxVw00 D@ 8*WONKMdp,5*Š~10 SMTzD gk3VVh YӖkآѨH[J+}mo[v(nE\ *q*HOQ 8R"1X$~'qCP$ !8 n ^257-e.` zW-|{`W)bo(a0M d+鰓 LܶG2ƈd,(AiiWL& ʱxdXǹ9ύ#ØF۬f~yA<` gظSbA wIL@">~Ӡ =HI"(HA twҒ^tF3ԑMNzE-\Gw5J֬jaֆ-Z؈rcmeٖJkdGӶm15HawC+ HxQmٞS-F&A_Pw$`\@ ) dxY:^me6@ uWL`^6H~*TA RBi'& ^q` }D0@"|N~t@ $s)\Om@"Dc|܎7PA ?HPءQ7ԫ>@G:&!z/dS$9̍2d6i|yә򛟼< 4|5oyfA>}SOdF,^?@@9rg:Nzg~Χ@ݯe_y0ƑLLvlh;~AOtY>A07eZ/Ec &A`n o H  jҐ0mn`HA pRN/-zDn /b ^&.M>.%*pZ0PV(0 o  P P  % wKP UD$@avDem$E pP]+I$H  -JbFO0@h|1yq|Qz.Il+tOXOz!KOŜ?hL/O^OٱTnAqʌ!yor qbDbn.@0;4 SBBY&!"CbC;C-BPZ?#49nD@ FE @ 1e.GkNB3+E&>P0!BF t ܏SLT@ct TtBat`KL0MK۴B4t t UD`G}&;n"IO J~nj)bK "P5_&D5t@DDTSSUG59A nV43UW59' &X˳WoTp59sW YV :I!&55X`Q:u5" # H7{[ ~:[[gtN[E`}!4NVaV vbq\.QaIc> (,cD.u` " e lR !Cv"k픔_9_Z !J}L)C,_/!M b\)1P I@[ 6T7ZIc\eVet7M+|ua7<-wO wYyOBqv}\sp;(B_MwAE.A{"&0m0Glog2{ူ35g8mpFgLSW1!!o03X;7p?X>LEK6Ox]S5=cn5:kT}q @3-}և3pR#q!// /bP+,3)wXJX.Xux،؍J2#u+Kr;R #A1()Ғ2+;=Qď'WRXD@瀎,I*M+jѕeY+Y*9ey hs"v^#rh!2iYd hY!+v R!69@ٝy!˸웕A"a*9+y :9GAP,wq?"d7٣ ZCzSY~XXإ嘢/Z OZKZ ژ-9}Iw:ZکiZ--‹X sW˚RWq69 8}]B=jk;Zz: FQEYE7OY  %z+#mf n Q)]g.J)B )'[;{tW5U<m:C`ٙṟ{ %Ҹ9۞[{iYy.mLgjsO"k2#m:O\C |{qH/BB_z-xw|W480u+&&lc/qpg58 {1{ڤ<;ɗɡ/P8ʟ<˭͚ȿ}{̯|˜}ͽX fr<} u˓-\5Fr [Y&c6c&cDfn(eTƩb a xgG8ig魸ccƾQ 5jf^pe-dBf% d>dLf[޳29ih i^T>b >a?usg' ) v^TkG֦m&nPb@  oN@q&qPrb s0 TM$>tbT'J :`(s1ƍ;z(-vDF)ư1̙2C F\#+^4L*3Ϡ1PiТ1a1.ұdcؔ3jɁA<2J\89s:2ɲH>8F}R܌vU9ƢA 18Dm(m)5I{fN4oի[^qgG,5FmO} Sƨ}vgN~HtLjnܡW/|uz᧑#lW s4 zXf`~C]H(uY CF uw *C/Xь/yM7\qX9b.RzAVDH&YƤG?r8Eem10:ї1IEdIӒYKB:d#1٤YՖI鐂 Ѓ&hIner"h([`bA~4NE^fP-dkfQ,,:,:6g*F6{,&;m>k,Ѧ(׫mPr&Fߞ+mFo1X7aoޚѽ쐿otFߡ4/ ۛ_ KL-T'] -F2:Sk1Aͪ$3Js.C`>oԎsHTO]1>;w)F0Fjqjt%{&Hڢ1yXժK:F-Cs4!ǀ8{7F hE.C_QusF'+3;]I/C7߅~~>s:n#9 ^z|'UZ 8 j *uDfWX5NBlg*k`X@$M Df{=iI8&Q88Dtb8UДL8e`}`ڐ 'UFr ^e,}ȣaV;Q5{c R"Z$To(Ddi nL'26q|$(9JlPIG;nRqeBZ2s.1қGCKTR'ƄRS^2sH> t@gf1!9rMNdS#ԏuf$~"GÂ*IBz%DaFъH(Gx4%)HMT)KR3-KkZ:T%F}j&FC%NZ&U ajSSnJS*zjuZW ְud-YVulm[٪ַutEk\׼z_ v|a[*6]cJveZjݬgj6,iK+Ӣֱ]_Ia d Z6,o{pWnʝ+sVDVBnV ݶb7nj킷/aͫ]tZ]ܮwl{{]"V__W%E"߮ flxFo{fX!0uZƢF\8X&*Yx2klƷ1{c>1sUYI>'KG(SYM2u ){V|1Y_Ǽ6{@~2qlg7z9\2Aq&,CЏn3fErN|iG[ӕ&37-T/:ҥnCjDդ洪|ްw}]`gW%vs\d#We6j)` cZ{f[vmWmԆ㶬e{cbݤ F~\x[o-q]o|ޞr\wW%Y=Z\fEenZ[☥y-Vg\x=Nm['9'{+gSޖ/ks\-8./VyЍ=K:ȉ~Xӗx.u)F3Hx}[vu' /dzK@"S7 H8{b!(N=?~q9̽r?`7luWT YZeY]K^ǸI{Vσ5$* )^ o6 U]Ź9/S``]:;\]_eǑUHqav 6kFkxg,wVk xjVfk iyj؁hk#xk'xh&jȂ3Xh2X x,w~?h~0(<i~wa|ħUw$U{S|Biփ51)肭kUy'~}~zUF'wPȄ\[|YEmmaw{r7V zYwYev0 UU$zGW֧qknp{wpqH}pkyH[RUSP$7@xt!tyHTguGGh ؍XX7ݗ؎rU8税x)o8qYm[U[hyqUǐڥVpugx[E+)o-Yp/Ɏ4_اU@ >{YDY8:a\%؂bHcX!g`HY9VUكd(_f(cI\8jZf6؅loI+hC(Fxȕriu}XyYdIwsbi}jh ,ɓV _YxCّɑ鑧YIpxYqY~`yqh 9GiljHI`)VYrYި;iŜy՝\58ɹ)vչ_i`eUuȟɞ Zu miI)_2Zwƨzw~W}pO7\^堪a :ww V}wLjsLV` U(R ݀8Z!,z+(22Zn73?Ωk}(  *aÅ J4ċ X81F Pɓ(S0ʗ/[IӤ̚5o℩sJ#K.H05`=S"0pfTPfVUU^=J._5{RdZkVQ[~lnQ NwD =L!,3,22@oxxuZnnΚԄRhɫNnrĻef_XaҌDzэŸW_GmUm}ˣH  (\.|P!"T@ ($PB H8`Õ $I$Ph`K$I2AJusPjѠ`8 t(`ر+2@(҆\ٴP(`h-C.& tvkW(:Ad!m,A'F'F(F(F(E(F(F(F(F(F(F'G'G'J'K(L)L(G'F(F(F(F(F!*E"+D(/>*1=*1;*14(0:#); 72/./3566665530 . - + * * ) ) ) * * *+,)(((((((((((((((((((((((((((((%/11111111111111111111111111111212121212222212121:4T6<>@AB@?=}:z7w8t8r5o2m2k2i1e-_+[*Z'Zz#[r#Vo%Uq)Sv-Sx1Tx6Tx3Jj/aA@_CBaFDcGGWEKFGL9MT>R]CY]M_[XlYcz]j`mblyj`nqMjsAit:kv8q};{<:>:=??=@@@?AA@@?????@@@@AH̻Zy~dSNMOQR\r̔®ɮܹõȽHp`*\ȰÇHŋ'bȱcDC rɓQ\ɰ$˗*]œ)R&͛ɳC8R*hCF"DNBZt*ՠV̪eϯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#K\+^3k>}6MӨSFmlk׾t WObenݡ:kh`)MУKND䀹 w\x{Qی/+pϿbN6nm w`N!)6.` (bh≮Y4 .pޙǓ vcG :H݆+iH&dX(6y"u A/8PMɓ @^"#嘉(Cّu kTGa3p8 s=ߠ@ AS74F>'ES-m\ 1{,3X'Ơt~ Wnm&׬lu /\ބnx7G.Wngw B>ؘgXzǿz +VXKAј|֣2=IǴ!rVb'70ٲ͞@o\ zi9^ +K/ž3ZY։)MA Xw ٠-T,s0_I8+}O i*'i #_`DzV֪Z5;)w<e<:ѱ5`چ\{)MRԦ:PTJժZXͪVծz_ gQ$q`z:A| mL,[ o[ڱq+t#[t]EmR[+64H0Һ+_}iٹ9ִs+[ܖԪFG*Yu|VZ6c=e[9 mo[YVkӢ˓RwjGt -fj߽Z4@G +HjZkzUoeNVi}K_``Jz)aֽͯ*k\6]d26›eM^"ږAhX80gL8αw;.*! K ȇ>&4@ mZBigQr:1 F;Mmniy'qDLad_Dnތ=qQB,f,1p6< pP'P0r[#I_? sΰ0:X,u0"``iqWzq1nZ-}w+qk>{.*+Ub)F(hBS4,fc-̽mg&ܖRcפfwQߥadV"$%-bMVƖ wk9gaā+Y2/:~ߐ$+KH僼@r"*̐7)u?}tJeXUnȆ憐т%CWŇ(ij(mD 8b׈0R Y  _RQZmjYv{:q*uz9 pK$8 0 Щ:Z:mzZJʫꫭZ)K s)< um0JrmL}@]Hm4-2}NMKm=T}^=?]b}dceOQ1XZamjzMc AFME4sJmmՆ=~=V8Ԃ ؄}|׈M]r ؁Ԏ=є؅ًgq^ȌGڛ ۠,۳Ma Ѐ}T>P ۱lKɭŭܾ-ȝ Q םӽ=v\-h ލ\-} ی}=}NMg^݋ "#*]#}! t(0 6|!7d$ӧ A<>(2}tTGfNiU/gT>dbP0tU^`b>d^f~hjln>- *m[v.U @w|Ty^}K.uc@(} ~Z|x9p闣QG5~ꨞꪾ>,W^.­E*uvgjFey6z:q=;en8c'f{&;1g'wgs` :QhjӤh3hFA?c??d..h6A'.3Vu=q^wvt5RC 2p4tmbcaB΁'@$'mlotsEdE"on}Ec.Ogprd14sqqs[rJpqT&O0$ 1sGI$)G.7LG4K8IsFҊd;lwBmvwy#O>auhAiLQvNv;xx 8>'O.j`AtwWQ {e*Bzg{v9RfQ{Cu$bAoThq?_ȟk1Hrm [/X0CV( Xm:D(7'{ރ(_ex 胥!F% $ …m2DD-^ĘQF=~RH%MDRJ-]LXb̉mʔRNsr 1C=Y*Q`REl:ЂR^ŚUV]~v%Ԋޤ*VچV)=6S*T o_ |p0bÄׅYdʕ-_o`ͅ;?{xscƘMLztjŢ=}z6f5ԧ8麕\pōmw7o?sܸOƶzĹx͟G9Ӧ'ۙW]O>3,ݖC0Ad'ԫ lp˺?1DG$DOD1EWdE_1FgFo1GwG2H!$H#D2I%dg }qJ+2Kl33L1$(VP2dM7݄rJޤ+3O=3O;<3/$D4QCRE4RIMlT &4SM7%J)юSQG%TSOE5UUWeUW_5VYgV[o5W]wW_6Xa{TJ)/6YeRfZ,6k[ G%wOEws%w]vEu@y[mtwo_~Ow {sva猸b/6X@M?9dG&dOF9eWfe_9ROff۰9gwg:qhFhfi^i:jꬷg{&{Fꍀ>;mnm}{blfn=ۓ[\qk{qwkYɆ=Ed`9dafP>ld;"gXzQQWGiu faavgNnݙS_}߃xgFfbwfI7]gޫz~fSYOdlnޖ=_zw9fR1 <Y*zbb^ԁ>q$ب'f9h dzc@0-!GBv xb=d83ZOl=Ol0PH305'#"pi,')ά0Y.f80F)^WkE$&i;HA2hrd2h^cf_gG"rpq-BX,'ʈfxxiBэ!S(JCO)WiKW~|'`L3C*)0veΞh޲,$Σp" n~STG> ENz~E1GfS{'4>vMvS_:l>-f\/i38s^D'zP!C Fkц(53 ?[/tNl;2ZTtO)8`'CYF'~zZ?YG< %Spl5[kֶt]v~ EiѨ̠x[+d2%FoQ5;le)[׹6idl+YߵMmi[ޒ֏07eYhw[6m'>״Օu\rsemZZyη/z^n_S`WV: ozzf '_HbhPCmai .ln^fiɸ5*6)d=ްmib5^{ip̙ۘaRܤ eP|#MJccCw={w?|wy'y%g!?K1y}C?zēsUz՛az~9X$,h{7#'w+/}Sѷ9l"&۳O??y쯿w??y?Cꙅ= @>,@>S@h@ s= @ ;l@T@D@4@$@tZ࿷CPˆd4A4  AHA'lB"|B) B'B)4#| TB`„6AB1B3 1B#L-,BpC(C5B0B4C:C2d%l$C?B;B><v\Gw?x GyGz<{t;n>q$ql@|}L?G<|~$zHHHȂH>>|>=~rɐTIIIGlHg$ʞɕ,ʤ=l|\ʩDJI\=UpD.TcQhQM ?dM91˱.tKlh@9ͰԨl:-;R0S)YS?UԌ@GPIJ-TYlEԊ|(qPUF|6qUUFZ WMVeUYEFX4-|^,e(F< >7cf=`mab%DEVZ0qmhaӃP;Wn5o seW.MWu%vW*}Wxy"W{|؀_`^!/9Z__Ve(4;pQ  E9HЁD8D(E0=Y]<]`\_ԁCXl{PAiTV0m\Yapml>88 H6D[hD[[[eگ\Ѕ]E68BBp(UZSa_Г^` \5DMۺݺ܃HmЅ\㘅rΝVp܆`5\0X]΃ޒD]^Dh_ea^cMOY-HUYx^H\p %8O%HO8Q𘷀]D%ۘۄxD(ݨ [_8>8 ͵W6^مaܔ6}]U]#d܏hmuڂva@܉\aڃܤP)>ݵݺX58eZc@܆}X2Q>{pbWyXe=n^Pv@XF&PeeTb6cFdVeffvgflhPL9HӐKo gaܠnelf莨 gy@_govggI9 rfH@ pa[y`hG k| xgF鈞Ztt8d CG,ail ] P4Gc蕶Fifhh lU%M=j;߰z6ighk 1hjf jCygtt~fkVj)$l@vdžȖɦʶ̆`fkUȇ)O`=Aed4L= ln}؈6XXV@e]ٖmZٚY-~`v~XHܯmc>nM Ձ<`^ګZZ=ۉ\=2_\۽&&pn\nh`n_6A'^@61nNenuٽ].`._ cZpC E=߉^Mt^qރo YqWFvYmZc`< \`VO%hQjT`___p 5GOVFx6`` `&+'cQr(E\qZ(q>]&6tU]%!6W$Ft>Xh .ZPX)3FP_db0ndg[C2& 560qDcn%Ȁm=Ym1zM{jh買C^ ygov{olwnz͉xi6|(-ڟ굎y_~zA|wpȷyh|i>oh|z/s? s}O}E_hqF_gI W~-6'~7WgG ,h Bm2l!Ĉ.h"ƌ+jc6"G,ɑI*WZL%L.cҌ9&N7sԸs0Wzm(RG2LREf50-}hQװ Œ,ѳhyV0LٴaV 4֮ CHH;A+NZj\ئ *a]f87@Iڱ#vIb e,!CuL:u3cE`[a]5Ԡ#Ď #I ď}ٽh喎-?^y F8]B$١^Tزc~E8FT!Qs0Q֗@A{"({dm 2#:E!$}dg*tVA%Hˍ I#AiSDT]%K.zA q2X(vԡnM&)OFPLr%A]^֌%aS@짊Y'M IL2hJH"ڨ=*RzmThjih*BJ*zjʩ*Bj*Vli:+-e{M(ДUF(m1n,ʺ-2kzm|*/ݞ/[ {kAJ%ٕnɡout@YA,-̻DlBYV@eKa]b74p^ʃ@1i*_k'ޕ# VTLys .T9PֱI7}6FmzPsEa{Ay #^l`Wd >`[23@wrq\!iەnfɔw]{ֶsȎagUeQA9aQ{`O_O2Z~o@01`bxE @:Ap6}*DO  ?؆&!(!F<" %QL|)r+"C,j)HB0J È4vql̢88ʑG,@Xc@q!hDp#Hpda> UxP2`?^Ԣ%OEUR1+X&L-hYpR A\4&"9D\*"3MdN6O~ (a5\Ӗ;cOJF,pߨ@ 3%?:P$mHAp( zш"mE1Jʏ$D7ZюrTU)HQ%]Hmҕԥ&թOy Ԕ?3kjT-mP:S*թL'U*U%TS]jLӮuX5EъPjTULէ~ժeDםw\*֛5Oի] +Ѻ֫Ml\ɪXUnM(@ ׶\+Yk$a0yJ6dkUSζm-J Q Vq\F.7=s @NwG/+ld@ťau7Uwgx^3׺Uaw}_OVZ.x`. C`/Nؔ /#LW1"0kb,cb8z61s>1,!F>2%3N~2,)SV2-s^2Ì_b>3s| c,9ӹv3L"/8 (ዩ0"8HtBϖY Ҟ4C-Q3E+Dc .XA&0Q5M-lREe3~6KaH?ZHxWK%*݆C]6d3~7-yȬ8T͋VfI <vHz[Z`98SפּDS7>hm "Jƒx1~iEؐ9s϶Gqiۺ$}vE"p]^H~kOSV:Q3 Y$".$uQnQY2 Y+x;ϔYF cĠ$#wIQs;#/S>d.z3s _~_` N6]N![!FQ`^n!v!i!!!!ơa]޼١a!voLű B2 I4ڣCa ,Hm"2L20"I8E$V`'"|b(2$%M1 Z",H"$-*Z/"#aF/t.pT.`/^#b$Y9XH|@`ŚH.ȽMĮ+`īH CZc$?$6d$6D=cM۾DEZ$BFΙCdDEҚ+`F&d*4fGV1\dI..cF*!HB4<#׵L0*ݷ-1>b[I$I,Cbe9$؍DWjHHUr$~h]&M[ fgyP<i& &k%gb;|)A B j~mnDkvfa晩40"ȭE%^\IaN\zM u.ga%Ef`yZ2@m fhz'!0ȧɡ}\gyP_Pʠg~bhi[mM(4hfW\'~v>YfP.RF|Pڠ !\aS(%[JT> Zre]~/xI8UBC[6ID_ݙD፩JJRٴ؟f'gY9HH (]Dwf'کHHQb ׅ]-BNjYjiOzH)]婦*7Cص*IjꥢYJjT+ByHdZV*5ڙB;R!Tc-^9^;%b"eh@+e<#(/ 7^𫻊DF~Z&Y&l&T:l9tR,ZZ,I^.HZ6N2T\6)ul[΢F[,--&.-6>-F-j!QYaԾ^)T8e^:dn򭭗ڒ-ۚ-n-٢-m޶n-mق֭ՈH X\N"!-V=.IPᜑmz.&_.`j_BnSm͉k8.֮u.ʨ.*o-o6"Έ۾_*љubag:on oo-` ^o|ncN .0f 6p :01eW N0gwcp oH@./GVZvobqWYsm_!.pn/0JƮeqB;1Y./.ojqձoc.um/&nSdr6r#X/%Z# '2.jr2*Y [,,Z-2..2//200311327ϊa>>(j2G Bb2Dek0#NZ;+A4Oޱݝ붎ۍQt]8SȪTj>SgVw2oW5XX5YY5ZZ5Y*4_N5]ܒr6tf 5`皜#q=ÞNJ6c#nw/ytjr6f(a^b/6bxv6: sװ Ӱqc6零)a뚱qiqQpZJt%5ptgSvww7xx7ySImXwXwR4R8RwP7{'~}~||{E6T\ȅq^ %3_̇:(dTxEl4tLE\Ms{(0- T v7^x],8:yE[x]PFEgxdhg\| -X5t*ع_CĂsPd0Xf0 @9 :9kDHl댇q r DX@t؁֔rhzytGcTȥ@:{:EKD{T{:M1 $Ǫzı'ߘg:D4ǁ{d8 zTz;?+t+@-z/yNyS'yT l*tNTMAM\Ħ ͵S|ύw$[|ȯ<%MD\ƿ߸Ex~_:gyap#>@\q| ~ }z蟻⨉>Ah}s;׋z>f~8;[j0cP>W= D D LPAt|C>, @>?@T8QE!pIDlmC'(ƈ!rc mk@K.*4pU IbEFG"JQ j]V-ysA tȢ"&EK$J^N6LU)mU'OLll ԪW.4l/A;^KVUzJmZZغ$첆SVVd\yf :q8 K*ZW1uou˰;˞~}飬|[o9I;KP #>dP rp#@QЩ ի:1O3O .CoE?d')q>#m ZKRI/ sCJ(9162&f\b"`]څOD!D=ޓF6۔jU@-P-=;< ҩ=Ss@ SFUTRKEsUSY4 F5K[MU[CQK4XDCmvU# ow,+RKl5WWW`&bmXO Z_߬NZ`}s5vcf]o9FU Zi4PCk J>y 09 XVY4Nif,kWg^ЩcNw9E^{n稥ƚUã+fUiz9xh;GaІqڛ䶗k\w_1eoh2_HqGǤ_{?Üg}χ*P} Fp `.( mPABzP%4 QB-4! ]Cΐ1a qC;wC <,B!&E\b'F< XE-0[F+bxF4~QkbXE8l>b`EH&RI$d!H4RCdd#HHqad%xEZ/IR2$e)J9d ]JR/#5I%x4/HK`rN,&9dsl&*r#<# gr+prf l貚D7z|< Oy3g>O"$DFĠ(h>6ԃgC SըG9Q"!%D*҇K[P%H Q4(iOq:ђ*1(L3ьt jJwzS?*KjT*[*UWUNuQ-jVjSbOy֪DVԛ\WJV w%UeJת9e,b![׳v5j+_;ź=lv='%Orrl-&_[Nrm9VUo B WĥqqZ*7ȵdG%`b1jw3tzw,{B;b1Fl q}k0o| x-i_2*86p} 3ؒF-La6f0,;c8-vacϘ5qc=d!E6򑑜d%/Mve)OUr ^\]~ AXxe6ќf5K/1 _/Z@p#CHD0Y ڜhcJP*iIOҕ1>^""!0F^LQ EDX:ӵvgi[׽p`o^B "D>}i%nq76BiV:ۀ-7:ώv"r;%-7 n[ݸP ]0w^q0t}0p0 p pOܦ0 yV F p8`<@",,аJnʾ0 " I/x!t#p } ֡L nA"BHP:V-"\-"`L??q N"Jh-Oޭӌat!"tMz!"ϬT,Q\QL" zڀ#,m%}đ/l 1 $BBަC n,!Mo(a*1 t qͨr%L 4^%>3N"6DP\R&N()~&yvr>n#N ./AB(|)=H)y2NԌŒ%qڀ*R$uϱ-Ւ-o-wnrOrs:!5r@,r1[1<`<1:Qz˲1#s&3/2n綮Po"Mެ-4i->+1yP.P"|aQ8#Bp:~sS3s:q:R.x8s9z3= =N=3>s>>>3?s???M@Ȏba tAkn-blp'AKBOCe~!,3,22@oxxZnhnȹn{ŬRhLƱȶrfXaT`FŃƯ_[Um\@*DA #L !*&4 *`C40B (0'!68@Q8EIhH5!n .<a]" `]AP +s!,D((((((((((((((((*++,+ + + -1555555541//...0'E'E'E'E'E'E'E"%C$$@$$>$$@"%C&D'G'I'J(L(L*L,L-I"/C(0>*1;,1?.2F-4N,5P-6P-6P-6O.8N2;K;BCBH;EK6FL4FL2GL2GL1GL4DM;@LD5GT+B_#Bd&@e*Ae/@e3?e6>d9=d:>`@@^CA]IC\GE`FFdEGj@Kn4Ot+Rx)Rx*Ty-X}-^3c6g6j4m4|58[l?]i?cj:gr6is5jt6mx6o{9uej۴guݻx˷/HP%va^XPĐ#KLr3;pTKl1^ͺ@?3Gܼk=}};8V6^A9NQ>FK4ahg$LڵϾz=|[o=!(TG}!hF(Yfv ($h(,0(4h8F ״S_7Y 4 .?s?yiN% )ɥANBIPQ1ŠRh CBi$Y2EZpQ0?\\N$],Id0t)TEUT'q.R@?TZХe*&&K,1"B*Ԋ*Q@u)!j(. ,OXk9QŷԖ[ĭྋ )@1(), Ѱv;/5;W#aKOې*;9!һe)A \qZ\x@@K2L1zbYQ2&ep|RÁ)4ɔ.OܦTqJy5?s8eSW}uluZq)*i"@*4TjɶQ&b5apmy}bHD2&yĪ1>m9{N@ڲ"@DtLhT,BG'DC/Z}A1(!"S &x{A8|)yTzب}GS0"}lO^s@9 Sb@ $ ST43(*$qDAL@kx8gH8̡w@ H"HL&:#A 6q&O F *Dbf!Jؠ0:D_4˂#3rI4Qu1Q2%zKx>r+ 0ǁxTbH>qD!rKzR,Q39=ǭ1韉|?"ڹnI | JFx-[_z uzs/?;ЏOXؿ>&{O~oOן/ۿ?8'xg㧀 XG܇؁- ȁ $Ȁ&x (8.p `.\®{,L/1̿3L5l|p, *,(L%lF`4?v7LG!1w][<_qa^~m=d>TCRTtVKVNWb"UL1[$KBV+.Qu%leTufr%~(2`QE! }WB<,eXƂ,@)F4=>Y+S<@F],P]o4-[O3ih\s=rXNDgu/$[ucn_``>`;{Drf12(3/?#0B]K4%cu5G+9H.)aclsbc)bV`F88<8 demdhc6Ye՞:w[:e1;bdD?fC'6VgPViCCكhWhCl1<i| ׀ 1l֞kal= k B>DThD$/ dB Ȗ C?4_68:<@|>!B/b÷GB*Q%{G+:q]0}q!yQM=4IDz=pesh8Ap:'{rmc$b/tvOVyAY7Nx8"[s,4G(ovTpѓeL9$werry5J!DovpӮzt?#XWDz$+JMJw{۱ſ0VpVnLIxC'۟?_#` n! & DPB >QD-^ĘQF=~RH%MDRJ-6jt&]ęSN=}TPE} :6>UTUUV]^VlC2EVZmz\>aYW^}Xл4gFXbƍ?6[AlN@ƜYfΝ=ZhҥMFZj֭][lڵmƝ[n޽}\pōG5+]t ݽ:͟G3sLq+[ѷ~0@ ?+/3Zkt"@> ͙ 7 7B׮DWdEP$(/gFUbkF2H!$H#D2I%dI'2J)202K,1ѲK/3L1$/43M5dSM4ۄ3N9|sN;ﴳN<>4L=%A ECeΈ\QIӌtRKŬJ`(RO4M;PG-TW=UW UIcP9$Cu0( I\iK<-ǏZX SZer~7ՒKIv+شwEVYfR\rE+ wŲc[|зs5J-D0 7{a#SZKdDs^EJ\vYcpße v\À0hW f5|6CڒdJJ3/|^MJCarz꦳D; f7[R ;ֲk,% wD|_YUy9K7=N4{]a8ג]Eί<_(CHıcrDԲmϲAmr+^$׊|a}o|Jt.ͯ.um2ה@Rnl]J"`$JVұ?*u*G,%+4#ꕌ*9M~YS䇝p7S*t]WƔPnm+$Twvl}L px8_ӥVYj[_)L]k ,P%]@cS;6vjiZq`t[` KÒ~*\FvW8%*Nj` -"׸|ݣZZaإ!< o+d˒y4žW pJyCTرDw+ |7|!9K0~\E8O&>d.d4wcȊY$x_|Q,8S1 k,rr%/L~2z($2r\- p[1nʘ 5SR~sܪ9 ƽ|g%Yt~%2dvjy7oLKPCЈr;6xM/vË-qYGϾ3F7"x7j\@%79PrZ\.9w- ;. p^yʃn򡏼?zƓ~S rD"Qh!z!h}e^lګv_jzݹ mwLq<vo|~|y‹&ȋ&7/yoKw{aէ<@|wS~oy;ɷ~~s_—~O}ٓW}_~ODv>W ? @,@;@L[WkG븏 45 @ ${8d8L8487L7PkPu@ <9@694A{yAxAk_\7#aTǬPH Njܛ=% 7vԋNDNpEL@M_pI4ЊOPLɼʤ4%5UQ u UchaQHD8kHDr\aaB E8a@"Xe8}A(}J-@=P@S@P80ӂX0Ӄ9Sqp:UтS"5#͊a=EvEH+҂@A~DD@thu"mFA%C@&ńBT]dEAAC AR 8%@=U?xC]VL V]59%<]:M=i%j%Td TRփ`U_}ULYUPv(Mf,dHc؊*%aPWU=@[EX@BU5@ӂ~Uւ`fguփVkV Պւ׃-r vTQmWfl(EVoыUX Շ X?P-?HSٝ]ևńMfZMVS|XuT.ثDYDҬ8EBSyՊ5Z}QBЇ>MADD0>U=C`۽pKx ]p0CKCPܵͥZZPESCT[BH۵CAPګXZDdzeRڃH݂WDU%؂] 4WZS5Z?Xӥwحښ}ٰ=]ZւHY( Ux݂ / qY(8]UE(8MՅuX%ކ(Z>E^U^TLm-T'W_XW}xUڍ^th^Vc1'EE55Yr9Wr%/S25S=B`0naӈV=>5Cd@a~U%I%.ڣH0-a&*+,-./0VI2v 4pP@NڄC6N96:c2VﴉNEqB?HKdAGN7V>dE~cI%d9FE~NMnO=KdWW~d@e8clHe6dXeMY^$cJZcJf_َ]N3j.f3mkpN#)UdZd8Teaew>De^Vd8 vM0>g Ke`n|nv&hbncb, 8p묮iYPlMLȖlLDm̈=LЃ)ЃmmFlv~>xƃ* I̦@k4΄`9Ƒ6F֎l.*`Pv`\nyDnn'(pYl^^H>> F=6 ?E=kO x*8VH=@b r{Fh O.PVml7q0pL(@khl΁.t`^tոuL.sǎd@>s~8_.&.=qPoVmLtL`I^&  eH̭si'PͶS5?X䇵7k=Xovw6jwiG tSݮ~'7Gx2#qN Fe^`61N閦6dxe,M}fegh%;.QVϙ/ ur6eg?]xziU6Kg7tRf炞'z+W9fzyyMypς欿bf'>k>@Ikz^nw{yXggtyzwѕy|x:o0Gԧeoz&{n|%cf捗~~ԟ}x~q~oODŽ'7GgwoO, „ 2l!D#Rhbʼn7rQǐ"/bf$ʔAl˘2i3f͛:EscONitaУJ&]aӧG1Qj*&u1t횡Q>KvٳSղ%ΉAz%֫H|zUI^ wg~ 1۷g%S Rf7HB†ǯXWW.@D[[ BdݲoNUJo-z+VEiJ*~:ꬹ髻bk lI+:KlJ[2 vײ-l@X*…Z9Мahr{V[/<{pK-o" 0TW]&AHq bKnU< d-D2 i sHfJ3@7"AY.A]oB/)uVh$G@vj0W_l6* Nzx +;vV2\sτ{T\|I0ͥטT}e[|֠s7h:y'W'P/#3엑܂y߉> ԊgȬȗB@3b?YSR^fv34To?x0/دL/ׯoXb1`: w Y?607U@RpS$Ą S.|! cBʰ6!mH>(! 1D<"%2Fl"(RqS"h=^1^a(FqfLaϨFqfl8zqtĢD^%$6A 0z!D*rt##IHJ<#%/Lj2<;0B5A I9l%aWʒ"-oD]F1d!Js'h*He.t&} M6N҄5Z0 $Hy xŔT9N^S;YIӊ!aJ6"'5?0s@ jP-@rЈV$(U&:~(C9QLhiIAҎ^ )H=*R#\)NiҗT0}JeR DmL:Tԧ9eMzԨ*H]*VT>55-jMzUtajUTj uGݚP]*Uӳnծhk]ּ~M^R=,`X]+]Jж5mf V#8Sxdsa1,!F>2%3N~2,)SV2-s^2,f 3Ӭt(,9ӹv3\e .R\8AW)A2p8s !U,En!u!!!!!ơ[[!>LI0*$Ca9ZU?(?|g\F!VŠ&2¢5A$RCX&^ x"(F#b$$I0+#z,)%/*]H!Y,l\+hjX*x.#"A$A!UDNܫQ䢞kU䚷=0UZ:R/ d]ݚ2IBM$AZ"9cB0HUB+PE+,b<UA3Y4J\?:JޛOVA֣훡4TeUUD%M%UVjeuA~E٫UVp%TT%eeU`x)UYjR)A5`B: "< ;A8V%JC&]cJb0Jd~)i%I+U(^#%dj\jھ.6l ,s]Yb"VA땝gCZȉn,&AΖ[>--&.-6>-FNm|Yami-նN!kXabc~Q-ښm!rYZ!-v-mঙPE&N)^nΙ `i'چv}(J.សVY8j R Y>nƙ֮^~.>n_jnXnU4-/.. o ga.¯a/ɝ/f/}RF 60h-T >Bp9\I,YgU_0W 0 0 yphqp YcB#ĩ/mpn-k&.0/->Q3//9>1qnq/1/~sׂ/^1:rg+. WcN2$'/ZY~r2/ 2]r -n2vr*[*!,[-2..2//20031132GmІa?Uw%34`sb*U ZRE,Bó_W;r4yqdUtRdA_#4],=^)5eybse_=45sft.0=3Bg鍐jhih!@a3k4K>oj)Q$4nK&L/4<4*5@.O/uB^>&^~3S_5V[2g5WwW5XX5YY5ZX"@N\ܞW/J^NG\uͱ40`/vv*vqvfr`ggUu!JOfcg[`kOkRpbpp#a;(5cvZvdcqd6r/q &vrOe]/^Sv/iow7xx7yM}x{?zH{3P4OTw7Qw|/~O |g5@$:\]_X  `?e`sE_ DoxHb`@L]M 8k2pxhx@\AbkP @Bw9ι_\8#h4yÐP xL`09bHχkPÔwgw'#C14@$91$OwdxBfl_C!܌@Dm뤇r0s D8Pu P dɍ/r4z:|zӤylzrqЇIs@ȼ::|@} 0::3{C rt:Ct$ijG{t,GMk{:}ǹS)<)žâ;I{zzl;ܻGzLͨX9B|zAKO[#o{c  tMD`sI{ɭg [ d~>Ah C{={f ijcNθ} }Ek{c'b|PX@hBt?|?˘~Jk2P?@+~&lqEa^)QxqbE=FhQ$H-0%+HaL$`4 hC?f ZqcIFCbL:R`t u*W8΅=,$ٱ#EdJ-`JKU {ɕkə6YFY7jUfu_^١$K*鄈ɢfXa=Դovk9a\3R歙9wr>ĽZ`k1F>vEPRK͚W/]tI _?~}SLN^K=; Lsl_S@mМUT OAu;>V1uS>:Po} _ BJWL5Z-={AUU5UMUߕyShfb~_|w|>z?}?h~?I>L+ w@(Z Dm@Vl l=B%4 QBP-t aBƐ5a gxC9 %a#&:\b Q xE-q]x,ыc$x+QQdcAL``qFtpLbXD@2$ CD&rddEGFS49Lnpa'=BPt)8JTP$a+]X*ҒD&8KAꒈ%'`ї,&/dR qa*r<\f,lr)jP'h- 4<>Nw3t'<;({s:1ONğD:sń?5v&g>9Qj U(A?Q:4]:3JO4EJ)z~ HkJR攦 )"ҍ(LjО35HSBEQyUB[SԠV2UjY9UkN ԗZE)ZUjU`u_!:WJեpj[ZSqkVjֹ⵰o=ZWQA`G)qBr6 g(ORrl#_ضQhm9[Cꖐ o\8 5qkH\IfЙvD@;]l%u*=v ^Qh^!Wm{Ww}C5Nͥ X/\FX,0-a:6+ kqk1|9b-vacϘ5qc=d!E6򑑜d%/Mve)OYǹh,e-5 e1e6ќf5#.p NPEtJ \)&B< k6t! j8!iIOҕdrd'`òthLT C\KϺMlik]׽_LڳDdPO9H 9zv \c喋p[Ƅ*l4|>ᆴgOo^nty .6pza8F ̐ ĶIr-߲-?<:0Hd m{vm [E7ёbDT _{"6H` 6cLN?E0,;:q()B}xYԂ7x/w!yO1yoAzя~$/G:w롼FD<65|,}go#>F~|O->sc|?/g:7{~s՝N/ՌT U0I8ȳ0 l05گP"a$60U+o/VpiOl0upy}0p0 p ܞ umNa0 &b`"LϬJ|ʸP/ #p AdZTdp!#m ."ᙴ $DLm:RM"XM"M ;q;1J #FqdKqd! 6 b1:T$Um$TA -ьfN v0A Mm"'-q}qđ1׬)1 Q&>ñޢC .R,!E/A*1( 1͠Kr% 0^%m6h/n"2Ns>>>3?s??q?,@b0jL'A4(T2'L,AtA<:t!pDD=ECPEEP\BgBE DKHTGct:Ї,W5t,SGw+`GIH+FApBErIAE_,4EˬKT&BNi44*#kOC/O @TBLO5LeK9IJͽtQˌz ?kCFѴE=F5UGLSFqU ll 0B?)4CSW-(%Wt{ {5B5WO5׊YuZZZ5[u[[[1=57H tXO݌J`jls\5#Q^p _8p"аtq9`a"Be%Eg$Ik#Lo#Np#PpUm ]ocokq!en%am(\k5Ff;>_??_@A\BEUDJLDLKGLLKJRMIYMG`JGgIGjHGjDHk;Or4Uv2Wy3X|/Z0_3g3k3n7p9t=z>}@CFILPQQQNF;1D1922222222222222>?????????@@A@@@=>@@A@>=>><@>:};x>rF2k 8bA>Q Ijvt@bk E߆ wڹ /CA֍#,3ii+5 I$Dc&I~Ӵ ?1GܐELyYj'PlnT @vM-SB3#˒uDLYG KjvPG-TWmXg\w`-dmhlp-tmx|\>PB@@`*K)]J[>b7N^CC y]ӾxwnwÂ;C.b\̫yN7|u^K?:#Ds&/䧏+_=9G?쇈%s 򌂾0R(x>w l@.`-8 .Ё`BB ~P %H ; eSzDwUnca܄{"J9|#N}X(""=gŷn\]H"< @b8 lpЧ(ҿv<0ts IBL"F:򑐌$'IJZ̤& iZ'?RSaѕ0@Bhf+1`lb @)F$n 2̊\dIk0ʯxf,e93D G8\Ȑ"0`L67Ic~e dwdFas>cNtF7h *A$ZCZ%24DFrSzf#[зd !Dڐ<H1DS`TD@:"ˆH0 r$ye0Szt|GM*,TԪ.ْXJV72R.d$-  X5ZsY}\6? e%'҃.VT ׂd L)fȻR O4'RJ¡AYUC\6YIÚ-Ct嫅 O0܁  X! ,&,Cc¢06}l3. w.q}콌v&)/,UQC_CF4DPc@r4l!(S(| .Z 3Df֥e?!^-R / eNC(YcT@L"HN&;PƤLLkTG`=y˜LƲx81́T3N><¹A ~_,h̲Ni@N|@Q̠M/o)b?ӯ4ޭڐ4GLEH]-X\ZH;W7̰m@ޓ#ùf;ɜ+kWAl{4`y;l|;-T!-s݋t7 nywD1㞏?Pq&]UْeY#~k  U{Yv\^r߷vZ7Vv0 Z7 uo O`'[w&{ Ȅxy lRv Z'Cp xhhv؊uOh [~\8(ZG[GO`AЉ` a }_njm'8[ZwpXAjǎvzָ(u(x]{ZG~085󘆞xHI~ĸАY[`  ]`v_@~uxO()ꨎ\@)kA9CYJy[ǔID9 FPyUi[9HV O9]ٔaٔ_yT;η̰ u uH ke)ff9ٖuY_7ɕ)əyT9dٚIN(Ƹu[ ) 旛 ɛ a ڸXYI_ [R\7 mvٝ!ZZ H9iwըYvɗa؏ɂ ʠZ]-IPܨ}c~(]7zoxv]B(8#&X٠Mtv`gv"*tX]gȣJpY~h А[7EZWj̩u hHS vVшrH(jȥЇ] |`GdZ@8{p{LvN*w nvw 2!8jq T P 縛j2pzZ~u] Zw 7 Z *&w (JhcyWJzZvʎ ڊ*ڎ:7抯ۧ\LI(FH-h*yʯ9 ۰ZzF9۱ "뱫6&{(,.l%2;#4{8f9<=7k@;)+D{$LF۴PQ;T;V{PZ˴\۵G`;b;Ak]%%@b][pr;8[vKx2|~,[gB`o{a+Kdk˹ qj U"#02}@.!.MoE"^q3)XBZbZhuGF'2Q) 'UZ(D.C)BWS~ZZ0e"ubN_i\u_HdR+uC.m~2oU1!g0-]0e^F.@6Q_/uC-_UF`! 1 ݮ1Qbf43 5KH2Z(46JsC.V48fڴʾ>Ԏ*LW؞HF-ksgS?מ˱SgD.fEsA% n HCFq>r 'C&rigfGhユҖp}7 o7Q:m>i\5j+ctC6bӆ nf)$5mY|'4\,i1{E E6R)o.nc>T:h=wqRKOM CoDwFMk G=4t`xvpwrr?__-W@E f>@},6xZDӚd1Ode 7E_1z?_ȟʿ8:?538Gӟ@c޿2 ZsU5YpRc'/VAmRKo X DPB >QD-^ĘQFX RH%MDRJ-]SL5mY# rTPEETRMM6PWp_N^ŚUV]~VXe͞EVZmݾW\uśW^}X`… FqbƍrYdʕ-_Ɯy2d͝=shҥMk}ZjթY:lڕ]=vnޫwZ"w.~\䋓}c\惞Gڻ ^|㍗7O{r˩Sgo2$a~USN1JL%= *F`L@UT1̛_o1H pp?$o1b/Qd?Ci\ 2HN&A|d1H1H$r:K0_fSo(D91!1Ŕ!͛HtYLOfM̘5!Q4M^d !œžA('{tHatLξqo)f4]QF m,AF%T0GKU\uYuV\1SƼ1oaieXIslۖnC!!ZǤyfgkEs1GPt$YLt̂elLH xdw%P-T2 6SSD b^^-98cA6uosMyRoKyzlTL'7~˘3`1!e1PdL 2Sk",eMq62Z"~{b b7ʺad$JSp5&_{CDZs?/hD h3c(Ү5^&JߧmדqfI)QUcQ60I9HIo~Ơ7y {xރ9x.F {a>黇ߏ{m4U 0Ǿd~cHyO[ցG-YӚ"Ӿ/ <:SaG\ܲ7 : 4Cz2d~p\EXCC ~;D"<. ~=lC)&ъ ")1>2TWK2_^8(NiA;Ṵ\ !)(SBcرkoX&l $#HW}(Ɍ8b&JX;edR)өef-м "|,# $>CY$k" E!?bư% nӔ&8)/ab@2IgF3Ntƕ\'5]n~].ac:Y̫LTDqCcLGPxO|ӠqY#2=2.~2JxxNf1&/f~,`#r\=HΆn2;8O \hEҖ4~M}C=Qv(o Z X_=]:8m\ "ldب ]}dZֆvOz}mjʆE nf?N6 jSݿMt?]ot{ڻn7=x3fw .niF)q~=lK^xq _q|,/7kpۼ1yinr<-W΅t?"8==FWzә~D:Փv[7fW3Ϭ2b;+Yz{|SQ'G>'g:(w3S~ÖԿ]~kOLտ~ %??@uT{e5Dx`H;;(SL(dltV2TdAxe,|ce88l /;"4#D%AX*(*A$dx4/0D&?iA( *,\Úx4<=<2yDZžQ첣9×sILDB%43?5|?+CMT?@G@W|?@PL6$EX,2؇pE`4cd?+ %ZE O8Q@lmơD3pq$G&,tTudnj8 lxyw@z|Gdl}H1ǀ 1@K$ȅdȑh\(bajHssɑ职|Hd"ejH ɚɄ0Ill|ěd P8#ʥɜOoט0M?SWpx̽YWN]_GJ]u\uT`;OSIvYLL`GL[vivUhv1,uj'umu]lOv vYGwwpLIW.SJ:j;vu5xc@xn:lu|uuuww0gWygwwGvLpˤwz__Ptwy,vn{vsGwQ'uw?tlt`O֡vz~y{q|77{X@gwo{{|''7W|_wGǗƏɷ¯|'}G?g'XG_nx׷}/)q0/_u8}g~w_ķ/``f`ٷ``߄'Jw}O}gikfMNqzbM)U6l!Ĉ'Re"ƌРdh$ʔ*W,%̘2#i&N9wY'Р >fpL1F=fdמETɥKE1P?Ò:,ڝ.5:ZiDk NѪGezfִ*5H<+qLǐ'l˚!fF"2e:EF %ǠP~6ٹ۸)d.Dװe?xw}1x]:t皃>m:vnGiË/[݈)5hŸ0vy|z)}QL.T}9T_%G:g4H<iCkăQ+bEʘ$D&@%DYNYғZI%SԥOY_re_ɦWiygDi|wsZ٨hJ*cv:Z晔BduNzBiizjpjBԧujP 6JWeirQg9> cg~T9aMrShT>C_C]柷F&`hKwftXb|0Z2\a9qK%3YcM26nCr0D( / Z-D?3.8rO;q |::A+KҜ5}E-A4 Aj&W -SM0pEipPcvbφU6mÄԷ-Ӎc(9KGmyā[Mw+9啧j^ Ǯ.>yC%:5G/}7p=c{=?>Ͻ髿>>o??ç, w2~#(Ep_/epl@?Ђ$,OBp?b(p9sz-t!D q/4D)qsIq! (C{:P!V4'X4R \!qcGj HÌ{$!#.zl$!G6dG!=lQ@sC<)J<Ҏ+3)I&Rd&7)t{!tL;P@c"Sm[-o[|am1x\.W{s=(67{auE[\6׽wјH7j/y{\*5o +_W}~/,>03~0#, S03 s0C,<SPW,Ӹ61sc E4q=FFd h0E&.N;rsX2,1fN5z@dd 7F6|,( 8"|AkD E3ю~43re&aEft a AgBϑ3 AX:ѧ~5c-Y:G.̑fXdśH?- `E'BP+?k-iS&dDz,}l$^Dn8\UI37o :C7%}|BРHl}i@QSxr!gX"2v}~c E'H=?.b .9sS4x4qUcDHֈf|;:ԣ.C:ֳs^:.f?;Ӯn;.ӽޞXB/{{E^  u<򖿼cw7ޚG17=Sx< =_ɞ ([VF@_!]  Ơ ֠ fZ B鱚9u <d0a8]Dؔ i0E٭10pC=]an!!u`J a~ض2$ѡaa &ْᡩBߨ ᅑ5p5lF@6xa4]FPe~9,EHN[YDb!}ZX` Xě͢Eh,2fDY.3 Y1n2*':.\/ؤF\Ed2bD3745™'ҙF:>#(aaQ6bD6\E\^D=&X&\bc\$F$\4F/b!FEz4F4~E|$G^H^F7X"K?$/ Ed$IEܣ>bPA[Q$,, J[H!0daAX"eYVfϤPFxXe"ZvO[\\A3 &^^.[ť*ea.䷹Q$WZ!T&ϐb&iQbgU6]e\ a0^*[[nXYv iϤfnf`6@Jޥ%[.rnrfsvojXYRage'sfsDg|$anj'ZzqeifF|&zj |B&tgu姬KhbDdF(/G^E):^hX;ZFF`h%PŠF%,RJȑ~c ~Vؤבf9X1Ҁ0cFL\Y($dT~b Xǁ\!F(4juB鎆Zz?µjX]aRDžj(@ΩFn)gEĦXK~r1]>:Ģ)uXdF|-IYө*r]Z&%ڪ*#㮚ư֪E0ݱbkVn[Vrܯ:N⪵dnJv+N&+F^7x!1l*5EhkXZF.,6>,FN,V^,fFl FZB>ZnߩjhfnV%˾l~rvllf ~Ў,ɲ&rXIBdr'*h=̲U- v-Em˞-mmub,NE^֬r6"neϖAm^nʘ~mn.,U,ڢm*w6(:"N-fʶz..ӲWB$j.njU3XfLaf/Wuo~Eoo/_:@"&z.>gn7b~ *pK1n"oorW./U["0R_X k. '._ G0omc w0 7ےn荰ʲ.熰Bq, PEn [XU2,0m=mqqo;pq1!!2"'"/2#7#?2$G$O2%[lš/]gU2'˟d>"F(٧+"N+Bj^D:q'2ݝ译i$Z텃 ,EgEhiꤨCQ0 4dRZZf5ri樱!>2,O39]{t%b&g}2g[8]+'o9s9O;&\:^=73B?)F*\erߕRBg4۝벪k1*,𫱪FI%J4KK4LǴL4M״M4L6iA4P[ 1iY5Sۛܖ q=A^75V].K4^Y[_Z{_I6/>=SYZo_^c^_F&ү:XmV'6 0uFbOnc?XBI6e1Qw.igNj6kk6l6 MmmN7T6E+vJ~̃};hGD?]84:   Ȋzd~#+J CE?׽?=LWCI0`8~C=@,W.fOȬiSk6cά%@sPa;~ ddɨ602ͭui4H`s}Kl٢4\ೆoD qqe&}j`.ƝV8T|CƏY3=G$Q`֊LISמ}QDכ9U\>zu'SM'ȕn>6W>-p(pwmgKͺsNOA8K>{(13V QPp*_b&ѦszsoV0!/;d|ƇP+REڠ tpM-'!i*&`2M ŹJtrkb sL<4m'hO=9ˇSL2DSM8-C] {"@4L E?}Reb?'?/ UC]V/Ѭ5FumMXgS*.I*d 5pחz2ݲseN<{SUItӋ6u qQ+_4\DURSI#wPQx9x%b !~ćEbmcHxaHdUR^bxZdfMguW_:FDhNZ'΅`H~Tefq香z`}jJBnhռYfoFeP.?494~n&p:U[Nsd9s>MuJOju=|voO܋\yW=ww;G>yOgo4z"}g_>{zg,}ժ?ӅC城?s ~E@& t!A NxA n`=BЂ $ QHB-, aC P5t! mP=,+CB$gD%~pMD(FS"xEfQ04 , G8.Pka8FsdaB={$P.w/|O*~O|Fǯ=9JMꏟW_gY?:Ŗ<r`a OO/ $-ϼ"LA0EoMQ0UpY]a0epimq09/ui~*B̪!ΌĎ̈́m l® Lͨ!֬(A߮f! ! .LA>ݮH "0hA !"mh " N`R11 Q1DQ!tP6  J4 4 xI}2q l JN6(ȂQۆq֌t Q{z`ۮq,x ~1Q b dCq۲ Qí ۇ! B zq"1.":Cn~-s"R82$ `R$́*AH#KhrN2"ǞrNGn2*Q 96{-,^R6!I)2.y,i`}n6qLӬ.1/A.!H. hV!nnl6s1aA>!r4oL b hcTL3P d5 7#"  \S؊R4|P٢9::3;s;;;3}D<>w?L)M2>S%ϳ p)Or7@!@)?#C SASWBoDULt>C/AOF3T>=aDDsR4? AqcF93ELxG*ϯ Ȱ4oKԣTLJlT=[ptHIoMTIatENCmTB,T̡۴<B=@Om,QSO!FR/'UlPEOJPqL?KUIQ NTE@AuVݒU mDi5WuuWyW}W5XuXXU6X1YGYMa+˼Lќ erA ͐!N:SY 2[, [s o6k u  =M&S&S6fD\%-!2"/ b;c.9ӀO\ f+gceCRbˏb61b&2B ׀M^;6ց6R68qfqv5V۸-!!v-m–6 ō̭9_/Bsn2'Wr#x%r$THg #(pe2JRqgLmP#Z)(w& Ns/ԡ;g'8 b-1bw(67{vvՒrh2!uW!Q`A> 6S˾U"2ri{~{l3|v.}6wӐה jyT6[N51 b8S-}s|]ns5I5i)^0!tS6!wYEKxQ8UxY]a8exWiXmsϷ`SJ#uo>/USEUsx!,3,22@oxxZn{nnƱȹRhŬfhL[rfXaŃƯ_UmF`@@*#L **la^P$ Pr!B.e7Dj5Kq4Qv5Tx4Uy5Uz2V|.X~,Y/]2d4k3o3m0j+i)j'j&i~%i}%cz#Xs$Ro$Kk&Gh%Df!Ad=`6W)J 7/*((((((((((*+,-/ 0 2 44566666666666665555554431/.-./6 ?#F%K&L'M'M'M'M'L'L'J'G'F'F'F'F'F'F'E'E'E(E *A&-8'.4*.0*./+.1-/5/0:/0?.0H-4N-5P,5P,5P,6P-6O.7M1:I5EmwEmvJovPszPv~L{A~;?@@@DJOUx`el`l^m^netn}qylYGCCDEHLOQS\zŤοƱ¥ܾǿcNC@?????@@@@@@ɝwp*\ȰÇ#JHŋ3j\?Hɓ(S\ɲ˗0MIs$Dk*ِ6c ̇F{hLHŖ:JՆl \կ`ÊK,ˣ攺&ڷA ֬]p.ːoڹw5B7Sdhǐ#K'ҺLWZʠ/~8z鶡4/Nװs.8;۸s4j~Ŭră]jk=س3Œwr̸p^|Go|뽇_^{do^qQ\p c߁VIǘc6\oi&AX\%CLZ+ՖW(-e~yQHU^gȸ2OdEQ\-)T%d[Y\fUe[ZTM}ihlp)tix|矀*蠄j衈&fI,F*ivB:s;$@>d-x"P`>Q->4T6ܜP۬E<?*BM:@PTЬ7zcq:jj A{mO7+|z[Ch" e,)ʪp: 55D /:"(.$@ TM0nl;o/ \p' P '6bC7dN:2C+! HQc453ID@ <\_oG'=ι9 O }5% Ԅ#Zlte+4c@g'^wC~]9Ӫm0C.0mLWEDL+Tk-(C*MY^E\;^5dqo$;PfNO5NOcOS+@d$T1mϙ YZr5 H;T#-r%JE2HtyB28 I! e\-`-gD` *n:dQc\͆J}XBi iD ZZ`S85 !G/[B 4*Y^ c"c Čh\8Q!cnj1d{s@ 9.Q+]-"7Ih (GIRL*WV򕰌,gIZ&{e^:$10v KzeĊ;ϗ:!cz Zd0p 0\wC@:2i ؄C,49{Y6ho!ЛS)%uSħD MBmSBD\+BRE'"*5\ev0*]: 0?O#)Ԥ'P Y~JE*Vʜ")t5 f,JzL~hjYmz!>U̠؊̳b'a yƯkdSܬ NFv=6ZS&sXYJ:Ml׵ͭnw pQf r:Ѝt\RͮvkznwKwMzŻ}|ڷ~[wL`^`C,~0 b(.J1 wq?ZGp P@EsSn;؆5Z`"CFolyhNdžD.T 屏/=6MWTrwc/ՠV5 fC@j( W`nmx)hKa (._JSPEq31Y0rMeF I[~wc-kv7(.(8WٸKإ>; (b-|Mba3؆8!Xm+;MզkBŕEGfA6Eҏqkm_Z[au͸kVŽ Q (t#.R5._nC"sX&{xlr 7 𔳚f܂gWEʏsۻ82?I{Pƫ;b+.s =e+8ڏ]'#X ui<`gxo_-^-6QCZׅG^\ HbpzGV qF%NJQ=cz^ =WNyzm|7w/}c=pzZz`熐~Goԧq_gχ÷~'~H{x | xwkrwsq@#~zw㇂Xgw,7x9&hy7l)Ps{-,H1\קf}Ǖ wyٰ%DXV\zvktcXi'wexe Fv6qeXiq^b8\$}ȥņ&ׇmp1k3 33!@YL/.r3u\373QO ?a,9KD:tƜN ZO6lSO49[6a:q87o4S9' 8ȑH%us/7C!:\ɧ6i%s;S;F>><#?ܹ3\>@s>ѓ9 0C QCm9 Ј+ODGSPRTWE!Qb$pHd4FFGD"]צeOw6NQVZydZ4rO X6ؔ P-dtQu%زY56vE]Mlq"R[8FuظU{UXTɣDݚ]mQEWR!Uݖ[YNRMVݍ۾Xm Wإݤ ZD.&}W=>^~:)>quu$4N*T0[ >B5[7=lT^V~XZ\^`ENa~擂 W(n)H&t(c.m&?9ױc~9}@3Sq>_B+^Ju鋢頞'w.~ꨞꪾnK0^뺾컎^~nĞ.>~پ>^^> 0./Î _P%rb9Nv+ P ܐ 븾dP%^1 -o븾cf,2 p $o(? 0 0 Pj)@ +p O?  T/> 0.o?   n/޿ OS%.A@ DPB J y%,) Ldž%M6 RJ x>0ˢ+^%uVB`k zRRrɐǃA吔V7#e%լXv{'^fQkAFWpɹ:$kj};X-_FYfʜ=s~`ɍ@F&yiK*kJ9CM9[p.gXhdLqR[*^ߧg۶Ź;0;r{ѧWn|༔̻'%SO^ ʐX;恁d&y`r粑N:## HIt2%(|2I*dEbƕpԎ ,(r&D%Lڶ.ۅGC4L6r7tJ8S<"L$PA 4S5";QātSeUS9|0V/RJS}uUWkݵMZ5[1U`s5W@۴cy-Y_YiQP F=919m!iePtDfsF>=F$"r164 T E+ -}%H j 6/_)e=HcVKs9vajOjv%^浗 ^ٲԢ{zw7bԻv<(m 9Ynzp{ǹN9`x8Ct qW hWesOzwޙbv/s,[ы,Hon!VWyZ>p>H aWg^ |tK9gi}M &ht\^($\Ø@7$IGH˝J2A\`Dd,D4K Cp3.aWĭ JaJVxD&q(ED E-BqD }1f""n"$G!c'}h٤e^tgFwTҕ%yiG3ӟuE=jRԧFuUjVկue=VOַuukZ׿v=lbFvlf7١u}gW׾6ca`w=nrնYnъiO`jJj n@%v86mn~[h@Gxp:"M};%:طt3#Q r'Gyʓ6Ѵ;׼hE˩!%vXr%E|>tGZA ߚ𖹭UR[CޖU͂\{>v@LnĀ HIjH;Xd\z?x,Gsu| ?ye?<))ywv8#rU?kHc%_}e?{Zȣħ[:;ϞtA{G~Y+χ~_x|~W?~.տ~t?cd@+EHyXMjzv bh$b 4T@,S0LOX̻\'Hw(BkGK2,8X BP8d&)!l\ z@[XL3.-*T"3DC~BLJLCLOP$lKw-DR(y8Jpϲ7e=O@H2E(N5SETUUeVuWXYZ[=58Uk={`e O2b=3VO%yTR\laUVckPa4;%d5׿DRW\W $sF6hMv=um }Eo7ƌWp rWGWltX.IMLpXvEc ^uxX -!s}VT@Dنuy8ĉm΀Wd7HJ0t,Z ER1lNuYJ "28B`MXzMw]օd ʀWzD׋%ْd" ;ZVXZsmVE+۸׼V@[~<뿶uېUVgE=#V{@MVle\f\%tc%5EUj[uׅՕ<صץ]MCyyH޽]a8_w!^hioo1%_sHmM^Pn`y ޕ { ^^UaT.paH `8~]ݕd@[_pm _fi0ֆiȆuߔaFߖXk#_^ ^FFb$.i`$'^ ̆ub ^d8!M"1^l(BlᔨbXc(_aujb֐a@c5N%>R%^䕈bCVH.v]8xxۨߤ`4b^na7U9va;aeom[_(dR>e`؆o؆l``SK v#`Efhdcb`__bP_ &gZa4N^SN^mi@gVs]3H=zsXbbg%gea_P>6^@gaPӆugabgPDNfhjh_'Vf5l8鍞ddVcX 36cQf&~~cyXN!f;hn_]v0b_nR^`fbRdeh(;,xhޔ_f "6kg,d5_&j 6f#᤮^(OV VbBVFax^~lŦw1f\n_Пp#vgk-߻a6-{p iphVi8`TKl`V`&?]AՖnާ>&6FZKA\m= _#Vo]o'qU.;ctHtPdNQ'R7SG7UgVgpXYZ[\uX^_v_`'b7v]cWeWdggvdiZwjilenwd#\p7^/sWw[OuwXowwCywxA[ws}?~p1VJyugxlwhv^\F\PXuLUxWDŽzȅYOm`Ԗxzw?vWN\QuHؠHoyVwxxFXuboUy/`zt{_u[PUτyyC[xGx]P}uy0UXuy'wv^WFWUwu_u_l@C؁l{g$C |'bz7}ZGԗg}Ww_xPYHjUjؗ}?~huǏu~wu#87H8d8b~X߻KV7~?O_ԇQX[X{Cx}g~W~ClpC,h „ X!Ĉ'2h"ƌz%ҼV!F(ili0̘2g8i9РB J3*!UZhҘKJ}:*̩Q2@S~RglUUJW,ݼovmؿs ܰs[ྀ5t0fc3W^̹qǟE6z@s h \B=,y2%A& 2.,eɕ/Osqߡߜ9΃Go޻-rا-GS `&\6 Go^; ,PĈԐ * :`EJ`ZX`C4kkix5W A\#߅U"# .hP'(P հTBg1:!)@ 3 Q:4UDVb[rI$2.+)* P5&B^w-m2Ϙ颊!"|o!Xnw;( 1Y鄟 :X " .@C@2}@)x Eeֈ6=Pz@RkAzR7*'-݂[.Ev2JZt6Kj+򛐿CpF {/S2 Wl2fv9\/1(,r0)s4#4%T=s? =4E}4I+MtK;4Q?ݴU[}Tc5[k5ag-6eimsͶqgsp}vz~>xw3x܏C.#ŸN1O,iz٣N釛z׫뀻ճӮtp$$Gq@筼3_n;ԫ= Ep3}o>臍6ǏWI2;OG>f3 =}s g2*4.0$3P`}Ap1Ia\2\Pa _hB04!zC 1 wC>4"ϊ(E'1AXD&\Ԣ1v"(2.q`4cF6qr#0G51O0ڑ44 CDP#G:ҏ0%G7VR$ 7% 'IJ>~$*5yIH2<#Fd%,eɒOhk̫1yT3Mvդ5CW9u{B1.eNU:us'MsNg%68P3A4yNBQVEsh08΢(@AO?(P:8Τ'uX8ۜ(Me7)OӟT7P긤2M-SmZRV*Vխr^*X*ֱf=+ZӪֵn}+\*׹ҵv+^׽~-:V;d|ΰ},d#+RjAԢIl S̤ ~j n &OXVpق+h򶷾-p X&ogX2K6:LNPbWޥ 7~=/zӫdȏ-Z ;6' ^>O;3~0 g±HnL1$Lzm`ceP G(~1c,wE0xfE,A1y_Dj`,)SV+Y;ҝ _xغ^÷n~3,g#¹0,LLa\`8&Y0gŚE3ю $6n43g((.Ll1 ]`&>Lj+6cWӺֶuye\U־5-a;ƾue3ٔ=v-iS.+}msvgq1ZqH7-omgɽgS{{4^ kcѽo~39se#8ƹX}x B1. S۔|(+o9_sg||L x-ыLGyeNIȋQ:r|7'~{=qx.<=w=PvxLہL|Nv0G77WCmoA ҞvƆxÙ]@,E-B ]V1XLXsD(L~PL\~ay|-.L,D.tU =-rI .`  .`Lu {]L aL 2!d` !s!!t®%vW5l_]ł&. L** avLF$yBVD`|eiqH !W"YL8"D$~ %6aa5 |A'"(%&D}' M!b Bv*ZVu!6_ L5R%ϸ(X3߮D45fp73f7_؂c7::;;J#au?ߎq7Z+[b@!d?BB0۹5ϸ"LCc ْA  ]Q-XKbc.d JϬd<+̎ѤM&NNK596͙$d_O>6:"IeW1Z-ϰPϼA-|%UL $%aHEb'~(DTa '6))BvDaD,2cfdde^f4cfDgz&fbBYʕ{Ul-Da 0 .=L8$n `>J^$^!rdYre%gw!grUeʄLHgbuavng]V#~gttuvj'efbGyZwfls`Yq&W}5k'a0()zz*.+nV>lBn"*2l_=*[&,֮[j^l*/¬ ǚj:~+UA/6Jz W/Ư/֯//oZń; dP|Ʉi-hLh-L0gpU&IaL|.*jw0j +ˢ}Imja $AfA!~MX 0;Ve>dX Y(zn1 gWdZ1[w 1M`Ä;PI)hd菺!?2$UG2%W%_2&g&o2'w'2((cl](2*mfm+߲u륊/.,1ܾ+.3򲾂3=947nM355 $ko&.?s6M9Cs:?!"ڒ2d.13r.,2>F3[@7nu*ro,;4ErV4FgFo4GwG4H_IIItJ4tTH#ĺMN OI/NLOtOL弃1@0 ;ȀfB&Ȋx`BH B6tCl5˪.Bl*tWÊA5Y TKȨ .\uVWWX]{65 HHDav]b9v4y(KA\6]G f5cs5`hv=L6A$@hC*cjx @@+$ .@lvAB/+&,rB&RW5[uBpqI641PuD( J&̃h+! !d !dC@@w|ӷ}@@z{ wA0!8xKy܈Q[8$8~뷉KH D ą+xxx|$ ʈk8@x6‹Sx+Ȃ@lSyc@0@s0@swKuI(H8ēG 9h PŎćAxmDÉ 8Kvy{zxĞl:3: &ID9A,`: D|F zAx:{XzxȡJxDKA:A&87sy+lyTwԺD7μæDXAƚ$/:nzn{AAȹ[:nh;@B{A{;#@LL9B{b |v.@@ .m?DſHHg<|znJO@T|Ĉ߳=3ny/yO9sG=ڷ}klDzП!h/ؼw~ޫ~׳fx6P>ǿg>#g'Gx;1(ɝG{t*< B􈵯gGA@ !5ef.lH )K-2&+#b͐ڤcHBC(4<;'p5)G!9I5$83J!WzCM ֗Le%ڲK2%]5M)aq$IYre(mp%7YUMsRōy+^I}u.[ż-X^nQщ-V/c7FDE䒫BēUX~U+ by"yڮn:viWq>Z뭹{k.6nm~[厛; {;j>"{՛Eьj|GRsHۇұ4,IWSɴt4MG׺ƹU*TFRV.GP:U3YњVmu[WΕu]W}_X5aX.uc!Yڢe1V8gAZю5i Z" [i=ll'8V sMFd8us]NQE( ]6)<˲сo5`zM ̕}_"r#[ %'0vnN׿f2Pa o^b%DmqtXp7l[0=6 ܄d!$6Ml"vZSS"6(1EX` ,`j5C0kk'7t"|bG/Bm0w+^輆Qaޱҿ~]` 6b^⎅&'0 *ZЫN  #o aM,W& ,,1$(.0S-cl;1a uBP0\NPg,1f0z~^l/Mi 0N,z|@ S0 Xд vp 3j ] ʦLjj"L m P W,ʐ 'ʾL j% jFjz`ND5 =B LҴξ >KQIQN 8 p M {8$O!M1\ d11ђq/,o#l)m4 lq߆q+X`MX" ּ"4YL/N024o1 %b!!g!1$8$E$Q2%Ur%Y%]%a2&er&i&m&4L'q>zr(ێ'o$+mۘ뢲)8'"+*1.<.)Rj.,;1r)&*-r/../.M+qR/!r+*[002' ,230ݑ芡12i5c(XӍb)65Vs6[43/-2s3CS*,Ӵ|'2*0=S'-9 [N2.Ak@mݮ&(R>1+>u*c* 9?3p-$)q>U.@ 1s Nt)W*u۾Sb70sNtq93us42w+8?e:40ey(3=;wr R8!s{U.Yܨ6 I6} 6u6w0l56W57enD1;ny#w97~w{t{8{7ilׁWzW37`,yW-w 2c{S؃ 9%4}[_35zX}88xxtiDAŊKBiƋi&zX1F}wƍmaŊd=GAHY. ِ%%*!""@L& &%)f=TTB))AJ$uCpAF^IpaD"b:%z!-,D((&$$"$''((((()*+, , + ) * ++,/35565555544555679!:$<%>&!?%">$#>#%=!%@ &B'E'E(F(F(F(F(F(F(G'G)I+K)I'F(E(F(F(F *D,3<,28+2?,3J-5O-6P-6P-7O/8K2:F5>B=F8CK2FM0FM0HO2GO6FN:FN?FNCGLGIIMKFQMDTNBWLAYJ@[E>^A=`>=a<d;=b==c<=c::XJpt[yhdaj]mYmYnZo[o^qgukxs|xxǬŔiB@@@@@@@@@???@@@@AAA?@@@??@BC?:999=?A=|@yBw@s|=~@BDGIKMOQQQQ HͦP;ȰÇ#JHŋ3jȱǏ CIa…%S\ɲ˗0cL +V&@-hب̊Y9|O̫XSj5ְ`ÊKV">Hh 1|uxI-˷_<}0RhM (:}Ek1A!C)R2ta$Դ @lG +GεLԀ%ͻTB2,@Тw"R5k׀uس7yҧq\-5uYNC) j~1[`;9'HM&7m7_}P~`A1W]X}_Xr#5H4 -dP7ۆ V^H&y19ci:fCY|\v饗J`Upcxkw!Ԥ64zҔ4x@~4]I:Р?JOȁ_꤬gpqKz@g耆T+$P 2qYrf)-@0 e)-(g/ zW2## HlcՂ %.R7$ȟ>Q4AzV]j5ZtA HUT*l64-E;4O6cdٞ߀߂n'7G.Wngw砇.'g騧骷x%7!yyD |Օ¿n~^7d|@!rn߾?|3%~/䳏ҿoP+xj۟@]~ XX<fzߓ_r" A ׻O& B Pt M8x8 96O{γ @Mh? І: }D'PZ(F7jQr(HGЉS$M)@QҖ擥.)=a*Ӛ6)NsR)I} TtF(EO%D&gRԩrIPF/ eh!P2칎|lB>aՙ5U}kGZKc k3ygY 7݁B_H{_JVvF<$>Q4q$Xc)HB= &XU OE$;ِdF7 `|FPYÖ4=ֱY .vԠǀ;\ZLBru.toU bq^xWtvȶ?49 W:v{$X}gnqz=JK 2g)@1gSb~13Z;Ps;@c=r&P2;ˎ^ye+sS ; JD"JT. ٟVd}Z#NgJk'*IӠwE ͌h^3=Q[ם}g}hO:`fH݄{0© $\tq[ye^󢳻Pв[N%χ᥎2.򋓼;GqӞvOp9wlƺс~r|K\e]֤|Yw{EJH4{(q-g֝'I>;vo53twi۝-vZ}CԷlY[z?AO#]g K VOPt̐ՄQaiAƆmDO?u}Ssbdph}V(xiOyhg]{Rhx؈@f=z8xc&Ňȉ牤ޅxC8Xx?؋;8:Dxȸ̈C،،hXؘٸxXhX䘎鸎hȍXxA;z 7  XxɌ ؐiَYxi'_P )3J@Yx(y*ؒ2 4Y爓8=F( $ IhEKɔxL)5ّy)0 CHF D):I8iU QÈiDt0 ]uEj'HDd |zї)E)D yB9YyGwGvv DI қi9iiI̙y¹͉yyqĚx)ɞYٚ꩝񹞥)ΉEGi ڟѓ}Cɖ0i.+(ɡ%oI),ɓ}2 ]0$ /Z1:3579;ʎq@].-ʋ=IjGMOQʒSʑUW:[]j_ ac:=e=gjebF,KZs*uwpaʦŸyJ j߸ZJڧ씩ک:N(p# : @,:8 PD_@;ګH:/Ěg*)_B Fکg*&; պڭ H(ZP@P07Q,:Zzگ;[{k6!4 N ,{+N+' [(#&E$.P $0-橚l%16<[:9˲=;D95EL۴NPR;T[V{XZ\۵C8^b Ա ?CQaLPT3cK7,s6u 5;Il; !Au! j ܱ4#.%$Bq2y $.Q1.tK&;`* tJ!|[&V"1Ea!&A4q # $6#&X?b۴{B2%k%X$a&!q&+r 30CR+R;T#5ھ6K*2r+ 2G + +''+; ,2! ,Q.b2,kB.r8LE\ =Z+2130R3=켊K3 \1LQ66q7.4uȄ y{-`[Ȟɠʢ<ʤ\ʦ|ʨʪʁMT@#B3MC L3BCMKKL"Q, DK$J[CJd/KKJC=KI@,K|Κ$΀)̹6ΐBC ;ϟT̓дQ*<}iGy4x +ѩG=GF}=;PLЫ|LK4>3L5>tt,ݯ.ЮͣJs "˳4׼RҾA-=ΓMիcW]f}hjlnpr=פ 'tK!z=Jր]*B؆ Ӫ*+ڪ؋*1=ٚ|%٠vͰ\ڦ}ڨڪڬڮڰ۲=۴]۶7 ` ٷۜڪfĭmŝ-,q8%H`"$%]QN(ۼǍlM mѨپN=ݔ M>jp >^ "n>&~&^(,N-0*43^8~7F.E~@pNIKOnQ>UnY^QBb P  % .@ P Pq>s ~@ N.K~y}>xa ]R nx騮^ iN^~NP.ON~+bt b ^ b쑞.n Ӯp . .etpP׎~^~ڞ j /N "O#_jp+/  58=Oپ)c.= H  ɞb0>^L^6Lo>> ~yP%uo.?[~O>qoTO>N *_Nnjm. C?@O~. n/@Np. B.^N a/߿|DPB >Lbb(AHHp èH D-]6HKL5m|SNyOEE(QcN1h)c8I! HV]E0Ҹ")Aq‰E8.A{^M8.P&‹6pljӮU-/s3JkwBu:q`ʩ'#FZ2ڃeF#GvK5aӭeWf kɱ-#$&Nϼ |pɛ/^xj|Ff;^vWvwW&܏9󯼲T$е +5|0CT KdWp)K̊.!곊!-DpC*ȮB:(D: @5BIQHa+-/k\\J+"- z۵{!|ci| _ʾ}~域~oG'>$D@6Ё`%8A :p`5 ^a+AЄ%$ UЅ/` a8Cʐ74 qp:a1D!1D4bD%61Ltb"NAwE-`E!CbgF5m"F9uD!xG=}S0/ gT H!C|$Rd 3ICLn>4D!xH4z4e)SU|*cY"&1C"Ҁ$03)LKƌ$2qJW2Sʬ'2!)I M7n"'8y@lr: OS'> u.ڴ:ӉNs=hAyx@ ݧ=_&h@ Nb(Cφ>t%IEO>1(BYP~\DKщB4)eiEc:T15)KQT#M*P(]*O}T4\mjPԝ7 YUUͪYZִΕw?Wr}+^Sڕ/]2!4)Y@R#fY-~1*!цr<-Sժ]|mgK;DLX*Qr0W-;J҇l.${B=|o׋zּM|%W/E| ^ #Ip5awqEs0-$u%9kyi6Gzҕt7+Wӥ>uWzuw_t vgG䴷o{{CwwO>x+=HEH0x tC4b05y ?02 Ԑ<畎yշ< m@T@O'y?|:ܧБ-~,GcxbJ)զ.ѿ~Y?~π$?Y1@ыӊ~3d@;7Ɠ k D:$84T4>c4fj 4 < 4 _h$Њ h+Іx< 7XCxt ..­(C.®Zj h*` Іă *̵P}@ȳI7 F 4j%%<܊; 4@4 u 5Zt@Ex@xZX ZEU ' d` )DL m]OuXXR2H(>GF@Cdg ) hs[P1t/\G`Ů@\܊x c @pİc q|x<3k4H<d슄J@Ǯi0tTwp55x1 8,@ L/aGU G<í:ȝ䲄LǓ cH$8 n wx7R9HI Kt4 Jt@S< ԀʨfG ʙ>h@ʭPJLuE/ƺɼ4 ʮ˅k80sTvlyTEV,IL!=K r,̛3t'K$'CaLB=dpȭZ;9YKqƤU\-0MZMɔGK]aF@HgBC9CLy8?3*42"]Dxo? TȠx CC5C0C.5líoH<`XNXK ­ˁhC :+ C %5EUeu-n "J!|CN RJ7s>,O˼-,],!% Xl-L//(岂R)Q8+u(S0ҤE===S0,QVS:6T7DMMJ2CZXO$RES64 jT)-)hpX(W=P@ZnUB@KпUQN!KTNUS TS2EeD?Ihb5:m>VRVkD}S -uU3TQk50s]W13 c]+SV"%-"lVCXR ؂~lX!sҊ؍؎؏ِّ%ْ5ٓEٔA 5A<* K p]!Į|N<,B@@5"4¢]&\(7 vP/OL3$C3ȍKB0FL" 8MDe<̠['ʀI6HDJ MƆS|;Z{V|E[EX8G`K +ܔ\ɛl90̹ l SLZFܻ\hQ\h0Hvwt\Eܕ/K̙XF݊[\UٲDWQ[ ʭXJ\]YI{{sLǏÕ3$ɮ(ߎ+;=*0D4l|7*:K Z^L^ɳc8ݶ,_8|JDp3J( H(H#^`Z?5Z\<#EMU9TGڽ]DM%8@p~Ua^(L+ܮ6 3^F.e$0L\Ʉݴ]zep}TYU@h&h}hhgqzְhe~K hlh1g>izF]S&6jwVJxvVGhjjܭꢞj魾&j ~j& C뵦j66vgFHgxn1N蝮hiij^kv畞~6lk 6h>gnVh&vVjhzV0l滆6gpn`CֵF2ێ.jvn܁n@&n6VfoFo~oo. _Wowo"o Oo o n'cf8 7po7wq/jDn%q!o"7rOG%oNe `eP-n(8n톂^P^|v{0:2wn4o7GNf eo$S$0sn|}X#%qnEPFWs8&7t Mofn`f<$ l`.4/rzhf4wO uupuUnk랆-XYn{Pp{u"HHotENvwq/r7FtpS'Vfo8ufyn[ws^ xg/{_e|߁}nvv"' 0%nEHo|uy&rtofgRk`qn{n5 `y>ywy'$Zx}y#/%n0Dyk&& {uA/8wn׊oGW{{{/|7|߁w|ŏO|ȷ|W||G|5{'}?O}G}֗}}W}O:nx nz}}޷}}|6 Ƨ~7}ԷЏG~¯?rFK(n(Pz{&;l B@LP` 6P"Br쨄H`S4Ȕ*Wl%̘2Ai&Μ7kL@*(ҤAwhahQ,eӕ #z"=J,vP@H!ٸ>ʭb9up Tdq'ߥl(˚n3h5}#,kֈUuq%54gd!ܔ3 ]RfIێ$ʮ=zw]$ìX1ޡ$H̾/˟?~ҩ>PY6$T$:NJR}l!IEXa\kSc~y噗蘊ꠛy짛.zמ:wwz# |wj<:G'ϼ_yz7|{/}Ͼÿ/?G7=O@[F hPsx3ArPX2 s`6f8T&.!À0!3/%ߊxrօT򡫜.-b-12lF*0ODF9ʍ 3=rJ#*@b! 92D&bdHiMRSTW%ɮeD jG=#L PD3эr(HD +s *88;"Br@:΁ӛ QF=*RT yFUhaPP )=L;~@:hԎN@Y:ֵn}+\If8) !Ϋv$[B:`|أkD LAb},d#+"u (U)"!SP6b(0uxҶ-5J~~6L)GS"į5+n3$(H.t+R7<`)BmhxMn`%oN[M( +ҷc'Hg|cgʑs(HC3^08 S03 s0C,&>1S.~1' @+,>f"mőyqGnqTN~2>ZRD2sk6XD,1rE5g9tm|#9ӹT᨝\g7ӭu~7-y~ݚPF.[D#| Y*D1x w M|tHUt.qЄ571穒\8yW~j&fi18Wa,<3gZP+r- P*vSmp88H!)!RXaQ+bfnT0 ${G2Hʔq ƣ4{1'D5Ի: +?xԻsJIRêjRC %%=G `⃷Jv! D) %.ĀICB!k%iy WA[5Ѡfm$r"!i1im#Fa0Xbbmb#zb;@"5 5`JU]]b&*v|"7a(ʢ_h"b|a2U2BC0^-nJȃ:"3"6_]9_BWuD5EUu9:9ٞC8 #b#B:z#??*`@z>ޟ;ACU 2BSSRED $BB4YaW ryBh٣7# &nWw=!8b^JK`#9wyBvxOJO$7M! MBN"N$S%;CvDRODFJ! $TF XEǁ-~}\T$abb:}W1&-PaNamd fBgNfҍfffv`alZC|YJ7p0?AUPC<:DeU5>ig=ZQXYRatn'wvw~'xx'yy'zz'{G-{*U|&|׍}#|Vbl[y#^Sٮ.V)%Z6RhJ(^Y|Zh}ڧ(c;}$p# <ڋ#6aY(Q(h&bhb({(y-(.)%楉&!xB ӈV)Bhii$)=(6F)՜(BT^v)anۚi)*" "J2hN(Ҧ.饎S>֩ **7jjꬖO7ɪjBj**"M+*&ky,+jR^d}iBۜ褺(vZnib熎kJ^u:+zBdaFk츖ai>[kj69j)k-9)&k7g~X^>h,4(:Z[J,Ŭ2Ql,ެլ(+l"DGlOImHQ^-fn-v~-؆؎-ٖٞmzjMBH-ܶVECT\&1Cvk->%N!,.2$&DUa%pzUukn!v!@VaE].bp(fa&c'@j%jٮa+.ڛW`Cd2ש6B~waJzzWgo&g& fs^߲ޯ4-//00[doG0mk"%$<EiŒ SV.m)5.duڞ ۰ 헆.pi0 p 簧F5qUp/j: wq+ /1}j HEa0q=)hN.11  2!!;ؠHʖ(rn02LP@H2P28"cr@K"'3( )k#(+r)2)KXr@Q$|@$%IHFt(C/Xi C7@/6:>HA=@$3P2LCs1233C4ƍŃ0 3jx32.4J}s0OG;32s/sJг4Is:'sB/4H45[(@H8L+<p++LX-W5gUpsK$K[ELDMw;IG#rIDy@A0FSXÇxD^|9?̹D0 xĞ9IG1$AD:Ay39HXzj8EWGc9K:G?wDW{O7Ad DJ:bpDB/W÷'Bsyov=tJTDF\C,HxCF;[y^\;wFG{;J#u:SX;HD;[D?/B"v{g4<{<;/2\c9lp;{;GWx;A8|˗;{M<35P;8+C˻Dtc}=s=;{;/| ׋׼77㵇HkJ>A >oxԃz8+f;~{K|VJxUPw8GE_EKW;$D7yﺒ/9D>Cy7DFHl@0jtwvƼ:r(]?k\2w[׆=@@1`A&T ľTe5k(H [ň(@{ȐeK <Ǝ;},rd'S|9BP%dfŋmz,@Gw4J$o]Ƅ<2̬ LAzv`9vQRuk@")!+EcnEkWށʲSCˮݻ;\;G=]26t>qZ.oNLW[~3G`3Jh><2@E (z"3;Plxm 1 ,:AP# Π!p\qŐ2ik7%TG U^_WcE V eޙ}eE{>_7Nix~\UKkkZݮ8f=>X}m[ugo.8;flֶp'G/_:x70` BJ%4 ,x`( jЄ[` m(@vυ [HB+piep{L eC'ny0K&q7}nmdDˆFƍ]j Ggt$$!Ƽq]tdȣ h!>*2mTs Ԧc.ٯLNmd> PmBRrje^ɱXf.n\^,%` 3,0Kc2h6 4GfU:Ԧ29Kl2Nut;OyΓ=O}?P5AP. uC!QNlQQn(Dm Q%5IQR&- $&Ͱ6Z^Ա9XQ9 q$ RUNUUF +t3 *FF]#BrP:Vb]!ZW}_%$ KOUC *Kj~`T@cXk`ILgA~z5nJrI* QƺƱj:`|أmT5pO\ӦV na"Z=NsXkەפgɛ^l;SB5e<YԖuDX6`v"cj dv5] vT6pUC %6QLܖgy7QXw9s.y5=d!h򑑜d%/Mve)OU򕱜e-o]f1LV: hvѸѫ5gvΪ8!hA\sW yv Eҕ:]-Z*K)=J8җ6*fZӉdk=GCqzScǚ_cl7gv5AiB'@$mmղ.`Xt9ѝn+sӭ6nuϛo}p7p/q.rhZ 21R$XjjzӜ?=n8MkLSsɧ@eM+ Pc7FTm2v ΅}CUi&0 ^ba&_øm?fҭà_}@DO$'|s4X[ZK}vlQmt /幎2[2 p[0[:@ԁFJclvT$ΐ5*p7!oa,>KQ]a1eqimq1uqy 0 }Blkl ֺ!jaݬq̤6ўQI 1}Ѽ 1qeQMPQyQݦLQR!MY\մQ!0R42ì#K [$"qتV&khR~Gj('& (fr'k% "G6J)1 !-!*+jj){)$J,Q*$Kr튘62!QHf, R)qT* 1޴//r1!32%s2)2-21335s393Y=@F3߬ (Υ`!j,7l5: 04T5anp5N5P.,S8q䄊A- RK. _ 0:y3ך g <ʳ&+9=.>O8L*8Z#\Ovp=3L~ p:Sd0t.2T@Ҿ@, Q55 OOA!|3Zh/O=J8XToD4rkznԬT˹"dKe&P`ǐ1oά=# [ K3[LjHtԵL+‹N*j TpB FWCF@Em0YѬ=tS+S+*Ar5*´jΰ Ð5,R--SO(Q yH VX'2l\ E)5V7Z86IO}|s(5up̖su] qLUJ\_M`` `6avaaa!3bmbbmjrFqі0bєR@2d 3eYY,LP +?v 1%1dL/)20)d!O2+y^j`ʠrv-V̌miP~V)ϱeVTm@?M-q0v'H'-ζ''R5|&'VV& pi d#v]Vhomj[ Ͳmh4nt1whO2.EV2gQRtkJscVwW-dkWuLWlvMmg֘7ٞywzzz7{,X-x{ {wIƷ+ķ|w||׷{X{˗(ZYYWYY~YbPwwV'6B "6"|~."'% XX.Wb?#4BF7eI+4NFz!ɃLۂTrvC!J4CD9hx aRf6v@a A-DY"uŋokE2," !֤ L4|wXS1̘803Xx /V 8C!ab^ 'Av a$~8X Xτc_-% k b$䀖oSXAd c fyjy٘QR"_‘bYE0YO (!wNTZTUyUTʙM&!,ٖi䙘9T$ 9@ !A8Dw" c BAP暡 :9Y @5@ D Gz5JY: ¥UWzMQe:o{UqZc:myw!ګXmz}:zZ:zzϺ:ڮZگڧz ڭ b&Zyd$ii۩ z{%;,['A[zE[G{-;w!1{w`=ۨc{;afw :zYMNہ"wyⴏCZ a+ ;jعaHx|w%;0hy4R$ "@1)٘8 D6Ç?c#dC<9ec{\|M\g%LCC9|w Hwò#̙ c!nd7rȝC!A$~G՜<B KuϳΕYMOϯ%5Ы[gQ|%'+ ][&FɦCjTԽI Էifj㼚]ݜ`Hlz}~h& !W,D((((((((((&$#!$''((((( + * * + , - -/355556555553101213457!9#;%!=&!>%"?%"?%#@#$B"%C'E'E'E'E'E'E(E(E(E(D(C!)@&,=(/;)07&.<+D)J)N,Q1U6W;Z;Y;Y9V%8Q*8M-8K.8J.9J.8L.7N-6P-6P-6Q-6Q/7S38X;;b;>`??^@@^A@^B@]FA\JBYLDUMFQKKIIMCHNQt6Sv.Sv(Qt%Qq$Op$Pq'Tx.\4c4i4m8p;u>y?{>}=92.+)*s*h+V,G.?1222222222222222222222222222:??????@@@@@ABEGHFCAC@><:;:<=~@z?w?s>kv8ep:cl?akFhlMuk\ghdmfqn|vz~ǹúаkVRRQOMLJIHJQZ~Q H \ȰÇ#JHŋ3jȱǏ C0a(S\ɲ˗0]fFMNMQ |UӧPJSO9Zuׯ`V%UkV]R#!5jA^,*h+5w$KxknLJ#KP11;IU:f53九SY3հcƘw^۞O&;+ּ{I&׈'MKسOh O^ys\Ijr{$ۿ}_xvv >%`}=ȟ D!vAHB az`X{ hwe 'cx΅؛_٨#ՖV='D"U3 $^)dxfWxx\.dYwA6ؑ]ٗf'M|vt]큉%%-JX 5V'F%e:סٹ褔Vf馜v駠*ꨤjꩨꪬƪjkܪ뮼+,k&{l62 N+mfvކ-K-Dƺ _KコڋB;@Sl0pUcd5>̰[l܂J0"ȭV3I/6B?,<̭( +.;+Dɴm4)2B0!9HB$QL@kTL>]혓"Glk5Z|Lk1d ּ&K3JԄR+,bvԆ'l7Kz2 Qʸ]z9㎹; un+z s* V#=~:zԾ[ 4*! ? ZNB0F /_>A! w] (A ZɳU?5l6ս σU 5>ZubB@"?c 1!@#Rfj>B&jk^Ø1ZP|b1 'EϊX ABЉRdSXƱ|p$@'+ >#x+a=B'jaHm}d_'IO<%)kJҔ %,QQ(q Rrd%0uJ_Č%2hH eA(.rBA`(d.S 1yaz-өLtl:sOu;n3D'=iXS'@ЂtTHX+5Bn@GF 'DiPщBh03z[ Yː-i 5 JVZKQ+ CkSV׸о1}W SF`QV7H"άZEkZi[*Jl54HVNvk7=%]qX+=N485uuV|y^׼ftUU(Q]A\=(Z֖(m/uW] <Vk.+_4d~m0 " alEj1j5QQ!,B &*ٷ׾pF[6l.l0<9[_YJ[Z7ond+m}iŌ傹 ϊ!aY؃ 'Kۂe@ qU0f xxWCfbd69O6`|%V2\3.j3\:2̉Cs^ed/[j.;8s'BπMh@ۦЈNgF;ѐϡ#MJzҖδN{џu1-R/ԦNulhUZѨ~9=Z9ֶ5s-]վu-RأMtr \CRB6-NSښ6-mSކ4qD9d# AnSޗƷml~nu= VN`vij=QW\v5qqwMhhK7/x'>77xP&UyF4 d>=N/} JgәtO~Tu_]GM[}`Ϻ؛Nve׹DN'ێu=}» 1x_%?x>_;?vc<5_yл^;=]oC޳>ۮ۟y׏˷#8!O>~w~vum \c?n&F64 1 hBff҆ȦƁ恿F& H&Ƃ悺F&bƃ=&CH]aGJ8OFhFRHVj[Hp:Q؅bodmrj(҆npr8tXvxx80PH&y8~H؈!7ሔXxm" mx*4@"ɑ؊)H*@@7p<"08XF5{HL`8Xxؘڸ؍8Xx蘎긎؎" ߠ 6x8 ِyx YyNs،ِh!6 وwwp(9$Y1` &ْ. 6y8:<ٓ>@B9DYFyHJpPm"ɉ3 w +@ ypP5ƐP# tPDM+ yiloZɕ^fjm9`0 _ 0 )  d9T0T@apPiY  њ隰y)i i‰ ĉ ^M%ik4}9Y _T/ 扞Y9M! wɠw ٟ(q09işU :J J(`C0'g 9N0  *f#{IA ?9 0 \j ZQ 4S0D#IHUVA ǩIL1鹞~) я A) *eY 㙞1Aaէi*ʜQʪ0 s 1 )P5` 3ɐI6ۉ FZ|ɠJ gʭ:ЦV;Ҕ/j[{ ۰2{ i% :(k9(d'kuuBr ьPr&"["&)&k$<$$+3;o$$ADP#{$XP ^+M+c=k'8 akHޡCX/+{&e[t$r"g"l {kw+28ⷶ׷4+O%s'IR%KKIpk[kKkQpl>H۹;&Jhq&[X=#Ykn{g[1R(q;[{؛ڻ۽;?7ߠ{۾ Ⱥ;t(8 4kI̡+)\| "<$\; &4 l\:\(9 (IH4DlDF|di7ERL T,Wƈuw0`l_|a\*klnprcl瘍М͡}yL} |>鰝NyG݀{,.뼮봞ؓ.2q ᓎ||乎 >e~x`}{Ԟ>~ ߮N} ]/z?N ?O%__&_"_n.,O#Ύ.@{mN݀91>|՛.~ |G~2Z`/~0U]zo? .wzzǁu/^OlLjnԕoԌ \!׬ܽߌ מ~ h/@.@ֽ??ߎϳ/ǒqy ߻pfDzOg0~/_ms??}}! _D(IYD-^HF=ʖBk!!DuF-,]c%-3';:3bʕ3_^5SWś9iUfL%>})֖5'6 ԫmLyFK[|~ ;VfY=FeVPTZEbϐei!ڿkƌ5fΙv:gѥM?L&.$Y=n@Iwp⃇O%-Xq?LFv?wv͗wO~|韶t~@H=@TP0tApª*p 4 CD4 C-2DTE] R$C1GsDeG2H!$H#H%d%t2J)<J*J+*3L!L043M,T*mH6ݤS9s;=4?sPB4P:UMFN&,LN; SP#PQKTT\OU[UXtCG94qVD}SV`MaY5UvfdYAkM\suSiŌv[m%.\pmVTF]yőgm_eEbmWq`T필{x{Hp8b)~8uXc-8G;xdBJd]V{bAc39wIzYzoYde^塋hZf}xg;少Zꤍ~k>;olo;kg妻p>\ݶ;wrC|9|S׼uiFя"9H4ҐD$"aM#d$%9IJVҒ`<#_Cln4vP"8ECe,'>$Vre.uK^җ3!$v `r#Tx%hӚ#Mnvӛg8?>4i4LT4 qB#JqӞ7O~ӟcqs\g!7NxNbID%:QE5Qvԣ(A(ʙm KDچ$8r$tM7G9kS?jPKrjL9i"jTlTzRjV1 S|89Rx"bM}&=12%[:WgT -XsNجԔ8'$pwհElbأtld%;Y6lf5YҲlhE;ZҖ%iUZֵֶ7Zke;[c5o`çnp%d#VblJ5zd j<׺*\bB@0 b5ozQr7hq%ekL =ۛoysyGіبn%F a`:ĦQ{ު7eOyw flQEld56TB0vS8z C*O(Fw$.7G^oD') ?"zᲤFpqѓA(C;+\@̳"`9WGwWb睿өRG&MX/~E;%岄JWB8ޒvĥNLu*y @9sD vu/5woy6FtG#x"mI>QtT RJPu[Cby8^"K>|Wq?CC|tgzXU7_F!n{GK?}Kbزd78hˀbpU2vjI8Y@?4(g^C:@")H5N˄O 5T(OMA4kЫزg ]݀c`d@AhJÉFBȢ3{$O,01$243D4T5d6t78"rɨ:P!+-+/ @->:Xs>BCKKR0L'EORD$B*EE"EDJ:D:EAYZtO4N4~E>-Eae;m\E`$DEmAtFKEXlP|F\E?8k뙳y+G34N'k* 4̌;p>=Z8vrcNr;A?OlІk{Bq4O& ;Epvp+ϫ:눰*LQ@%(=а83˯TӍ\HŽ4Ku 9:mKۧSU?S'?O]QJPj숻܈ NE(8QUK@C} Aj4ʆ Ҡ\ء#FhIDɇX7$`X:%Dʙ-ٗYٔ9MI_EYIQɌؕC6 ݧ]XŗX,ƇLJ+Ə=Dś5Z4L 7'Eɛ|ZZFǶ Hz䈀ZkL:ÑJk[~.d نɋ|ę۽C&JIHZrܬYZCDCA{"Zɠܨ܎ȅuĬ]6e]5D֨;ݦ^uX? ^f+Z>%5Ž]^@_Yʆjh]]Pߍ]j] yPthJK୤^`]k@ZF`u0`TĆ^؈i^ jȅ kh`kx_TJFVNፈxNJJ፸b.kȅ؅ *vxJ3%4FkPc*kȆ_a!ĩ@(.f-7V%6䎐c:Gncj4Ifdd?=kjb@d"BjVeDVT`*VM~jcUW i`NZ[eIlVSE0n1dKFce6b(5no,>aU>ai\PE$Cf'n`9Ncckaa_&g*J p _ Fkg !ވ^[8fvbt4<~Tߍ隶i~i3&j1NZ"Oi"nj!؍`Zjni꤆.jjޠPjiJёkh!:3kkb&VB[l_͂n!>!F.NHnlj@mԮlǮ wɧŽ oPN! .VmBզGY1̙رnɝnnѹVVooAnoIfo6o>o톝mqO>p1npopop_o G\/spoIl"^ўq.klnyрk~ f.`n' wik$r1'*1o r4m6m%om#Q^*467wbVs+ 9%Ot^Cs@t;'N/ӳS϶@4SE4Ec4H LsUJxASSGSלcE@Dq[NZɖ^wY8 Og.'g'jM:%jP/90]vp#%$}YiҊR*ײ;oVi? ԞBUl)CwTMLj[ՏhWXUNwoHSkj7UA_sYW[7DZPwE29x+TjzX%>0ӥiD][Ů/if-jRd_b{S)*LD}ŨϭmBר{^"|oW T/!*hX|p̏٥YύuL}E=4׷}~y~*}:{J"bk+"V@.Qfc]\a3vCBVKtCKM}4'DJCVGT Bw*CXFby*B-[ 2d:E 3g݈7r#Ȃh$Y̠@ W:aD,Z&Μ:w'РB~̐1fwLZR]$%3JQ򱕎dFJUvrarFA. d/| 3T/aLYsD&9Lb"c33ljf6]Jcz$g1Ö[2mۢ9# t~Tǔ2)MCɚD$7INbS[&AiٱxS{GJO}m,; KyrVȊ)Q?%Ϗ֨)%JS j>DƜrei,ʐfRq5JeQԆ4UPuTTU.uOjV ֫ZRc=kTJm ЊUsYJWծVūN(׿կjȄZw2 &Cy{2RNfOϚ8he5J6Ѵ)l-$ҶQEhO[U4=.#IJN4-].h/O*u-MWmjcֶ!~cAl؍֧ m-LRҟ,^*M7ݼYq%RL11[%.~1c\V61sbW>1,!Fjx,)+GRB@4&kD wdb>3H5o㸎bv' pY`5G.Qծ\ QBfsf&?ٹ~~4,)J}RXvÆFQ׻i]XK"ug2Զi4iwZd&*35b]v.vMnmkWF.0ӭu1~7-yӻ7}7.7äN c)<9ms?)KY)[8S5d2k{6K\<9I|t wlcZ5:ѳo;չWt eHן*fg{f?PNJw8&;u.v0Ůo0t+q?E?}W淟Bғ_G|[_G_'h5dC6TCe^-B0Cd3(iA.$3"C:4Y:L6H0 4@R ^-d m`j!E^.`>DV ɠf^ ``eBG0br` :j҂.p`Π 6 :  jLDz! j^(SCf^4/LCс$^ !&"*a#N5+d=A1(C f/bd)2d26~;% A%^C%B̟D4Jc96汤K¤Cv#D.eIF_+f5AOA^0HY#I!|I!@Vr%y恥XFDz^V~VDQ%\e\r(R[eXe@:c"4a]]j`f^%_Σ\BZڥ4Ma AH"&dBbj^c2g&_ENd4MM*%eWf@U TV%6ҫi`&)Ixb묪L^)ުjia,UʥBgj%3xn$@dvh"jf:Uj^ƾY oBNlZl,\ln_ƬƆ&ާ A΂rlϪlc˪e(1hZmb&-8j]fnh\>S`5Heê?-/a`dj"H&ά͎# ց.m0QBn+D#d#Ԭ- "`F.YfZ.v)*-莪Nn fcrn .쪬hn*F*6jhvl"=$6&c*n$زh!6h^-n#0Zn/gNd5` `cBf3(ց,B em >E. c0p HELg&DE#S9X}F-X0Wi0 0pO_ff^ե~p*eppv0%̂0s^$NbO^"/nn pRb10/NHş^ _'o2DZr/" "o%r#f$$Sr%Ϟ"_je2''r( #3()'s*kr+_+Wr,O,Gr-*Br*.r//_00`11`2_N'934G4O35W55g6o377R7383ӗ89_39:7;36óCNLs>3@G?4A/RA44BB/t@7C3DG=O4EϳE_9i@zdQ$itDg4II4=4B4KKt8ǴL3M4><5ȁONJNKPM55RSR/uB;NC5<7TUW5O:iUW3{:w5X XotYYtZZt[k3GHM]uN6X5XW 6V5SD `Vu]W镒86 D=4c[d Qaf3e{6TaPKXQ]AfW6hwvA,esjvhvlk_ivzHkӶh6lohoiUo6kvfvm6sGlvq'7a)7rgU-s7mCpOx;usi7tcZyt7p7y7|_RnwzSwu7uwfwo7}w~8q|?867_uv#8+w85W6\x6T8TSR8RxQ8K׵W3ttt'usN|5xw-S[c9?k@s{UyAy?g9syJI9IӹFAy9S4yCB#D73W?繤WOzE39t?szG:z64:TCNDKteC$@ d:+[2bnop"]Pò7;Zل Gǔe\6h@ǻe]eE̥Zu ǔѻ{< ʷɪZzĚ4%]3Ǿk5W'vAh~f>o?'/?7??GO?F?}z)~S`mSLPHOEL8 Q R0S@E 5@ jN*3ȂA!F8bEh6ZA9dI'QTeK/aƔ9bZ3Y;3{L"P1iAEGiS'O@h`ҮGF;ld?QVb3f5 V)*E .Zv59L:^sL]o}Rn[roI_*W?-(p5>.%rcXGƁl ;NqW QBHMuc&>~/_0iS/kA4D%.MtE)NUE-n`[ǺEQqBd?21ȳb!ZX.hagLD ؁2JAcHGV#TC-rO[ACYG0q%&`ԈD42YDzk&Sr6$"77=Efths 9:T{K㠆dL:T~ 2wR"5z PC&c" Cu4@BCjC'Ѓd$G!a>UVD'DW:6Q$R#>Fqؤp C9(D4]64}/UOXyS),3aa#$Q#}@ LY:"(ZzJViX0Q!D-zSb-$ڥVv`r"l-T%fɫ-*̅Wz1-Z뭻n)\C^[ &9݃9$ )?>?ɤr􇷽kTN/. 0p 0ٍM!1POr-N$ٍ OPpܯ/p=PDԭ )] N n  0M~nMҍ pԭ  0ݬ$ pߒ 0 խ pD  ! $ '!*91 7q-Q1D1O[5QQC_gc1MY{oWyw݆u-Q1qQq]q]DZqm1Q5nAH210 ޱ}Q@  m ' 71P"q!UqMpn #2 $A!I![$ !ݮO )P)) )*M m+;%4+q N}-,R2$RaD N00 N*m1gO1c**W$!+%+}# 22>PoO| a,14`o a 08ss8s#^N:R3A&oݞ3Sf /Xr!M :!2i-9s8?Y?M?@e@ TzS@o'@BENB!B'BCPC)C;C.2DDKD׍AKEE[EEUDtCatF/FcEqttGQGyDEB~H4ItII؜4JtJeJ4KLKKJ4LTjƴLH4MLմMM4NHTNtJNLO4OO5PtP N P4QMQT$(4 PuS˴S=uMCOAuTTMUKQ5UtUYK_5M&, b @ScM]WWIuXXNLuYY?ZtVk5xUZYMTu[\UU\ϵU5XCVqUf:h]^a_U_[_`U``U$bj5`jRAŜ |>w.*2bbx6c9cGd3< 0dS@BZveUVcavdg6fiApvgmeKdQgegq6dYVhs6xdkdig6hhhoji6kvje#ivkuVll6iǶk6jgmiѶm}jllӶh$VmkwovkVbkVqwnm!n#roWq5q6mg+whAbղg'[I Z[Y_wYc7YgXk`W7XsOCb2@uwOuXWIoXW7VUwS%Sdty{|W|ӬzS|M}GU}C}95n|F~~~~z~wI~ 8UՁ9Ux| ՂI ~1XP5u?XTM8-exi꫆qNpȍ:ˎ8I\82D55F/8I':N"=R@T?Tx?BEGILOOLC82-l0A1212111121212121B,_#gt!hr hr hr hr!hr"it,hq3dm:`bA\UEYLIVHJTINPMNMQNKQMNQOPQRPQRQQSRRTYYYYZYYZZYZZYZZZ[ZZYZ[W[]L\`N`dTci[htfo}oumwlwgu^p\m_id_}e[kXwPH@??==@BBBA@@??@CJӲV̤g™{ȿǽ) H*\ȰÇ#JHŋ3jȱǏ 6jɓ(S\ɲ˗0cʜI͛8q3ϟ@ JѣH*]gOPJJիXjtׯ`ÊKٳ"|۷pʝKΑ# ͘߿ Lj޾2D|$#KL'vZ͗CM韠^oj5}۸sٴ޸7qu+_μsyV|#g~νe X _>˗OpLJ~WxhHݕo%'T{Ep 9fEZgv6[YI,⋲5(܊b  r@ah؍6ъB6萄VuXfɑ[)dYPLlfp)2Ix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+km;,l' 7 +1cgw ,$q;a&0,3(ls,ϬmHL3G4=@}q0Qk\Ww5o>/ظ=uh[\g|5k-wbmi9Sm=5^܄|᧙-}6Tswۓc]ywL朓q݈Ni/ n:ߒwN?` F4댫17z^w?ězS.=c_{_gÇ_~ׯ\Oxd >"_Q ':BX 787 bS G4K W*D gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz!(GIO L(Oʁ,g)KJ̥.w^򗹴%0Ib<2L`&Ќ&4)jZә̦6wImz38qәT/yv'/)zޒz'@)Ё&%ҙ8}(%"JъZͨF7QN HGҏ(MiGMҖ,}LgR)NwjS.)PRrB(p:I}T3U.SjUCpլjU]TV:ufjZQu 5(a<X$Pj!;xh D P$cCQ ojpΆB4h v؋FveAVd`; Z!XlcqR֡ek_VBaoXƖmۇ1"jX6wmjW[^ס+`K2U.tQP' G\^4.D[Q!8ph խiz{\$آ0 m`JQO7l\劏a!kޏ [!e㇆aC_1`6ufdu([@6ƱCcN9UlR |9X񒍻@Ws\;9VsCX=tՈ9NB|64+zi=7 nAAO!:6p8Q>4N8&Fh:9@/EP'  |ޏ9(-jmlo2m׆.gON}y[&*Rg+)*Wۼ~Hu?#^ fr-w%7u;D釪=`9x֭~u sNTt(@w:7 =.96}9Y z^~_3}ozV>f/|Kw~E{}-|\c{PU1q,vmg{{~'~XqH{~v{ǀQ'H"d}\fc@l84xXYe /{gr4y+y6~&d8x5%T؄7N(Xh|GQ=X8@0'l^DžNz b@FlYxFXA_`wvFwJc7tUwzx.Pre|nׇ䖈%gUU~vn({Olj(xXt{(v牁HdtRxXmPAj)rIY.GSy(l#e>skF` zfg^h!WU,[N xP4 Ũe8LPL"HOHQ(HV6m^X_(xa~fݨe Iz6Y9g\86XJF)Q\)Q5pa9IHy8kXWcWFXl%svs}W>r\|`Y6i_Y.QApEdqYeUXaqY]Cny%uJUZ\y|R{ b[ )]l})n`'fNmgQVujbIxva `A`@u?9RXЎdYa{%SŔTUV)R UU5eL%̙i2՜㉛Vb%IQ깞՞9IRYP9('՟iމکɠIGzZR: !JU#j%*QN ePԢ. N1O0:T6JN9jO8ԣ>NǦAN@ZtHNKzNJڤD RMU:Wz G G0KTdThZKkPmjMjڦqsuڤ:Jzbd7 tYJWjURPʨx*ǔc*K~ʦLZ꣣ʢ[Zt3 1*.J+j ,ʫ?t KOP|ʬκ׬Z{j z ݺ䊭j* 4횮 tj类ZKʯ[J ۯ{ [ {K {$˰&,{۲"{0+!.;6K5;9B372k=EALڰ;{D+P۱[{SJЧŊikmoˤqMsK:Pw+Oa `ZQ*VJd4^:QYh+˹ +OOˣ빩O;z;KĺFKPKū+˹K[{˼ڋλț;%Kㄾks[˽˾XK[ڥ$ ɀkM =X=2𿸦${a KʿCʧ 70 5 k4<6<6K<2:òv!<,_N(LbzK&8|łkK2Da5ĸ&K&`mO6䴪D2 [\ÁUȆ|ȝTK?<:l|Kn NK `U,Wzd+lʵZK_TTʩLJV N[Y<;˲ <˸̼|H[Ϝ̶<͇% LifYñq?] ϸe%|%=;V KKHP;z{M02MૼU=rWQmׇ؉ŕwNyל }mؑؓݶmקգ٩ؙݔGԱ؁okۃعۤG`ºɽ=MJWLKiܽmݿ$Գ ٝ ځ-١M٫}J4lml-mMLь .X[|nXCMN.N*~-).^']-ޢޖުvұY@>BDF{ԕ;Q?AT>E^}W܍oΫlMdnf~w~y-Wނ`.b^{ink܍m.MJN>懎牎勎KsLζ݉]~KY 䚞醾<;@pveMul>>K jDK ֲTf)Y ]}35 BY}ĸ4L..~5(>*ʼ^=aKo/bMK~/KD !.l:aYxo㣟/OݜQw<,K %1<KZ0;e\p,@>ο+5o=(To eh?! 4D&QZtS'GAQ$'EHҌe̔eAIBҦ OteCiP.bP(B 4r5K|d& Iau~1JvjdM3au(:d qL/g/QEF11@_r+v,SZDFruS~cnYKwHޢ+fw཯xz7oo;r zɷ5}o"2TiIVDMPGȰeJ.ҁ#qİ{0s` 3\24sɌg/FѶg-Zc6Gg<#Ћ~sf:4SSrcH^2{!,*iOvUVjå~5#\T SV"g-kxKO3uE2WTä^SNLj26qlWY1$m8a#!woJwo~>p|7x#qWx=qwx!r{7Mr/߷a>s{˜7q>s=ʁG`oS2>:эuOέuo#wp }dЇ1[l:po 5@Xvx`p:Bbx3~5;a>w'1͛\Oyxtpz{a]/9-1;yֿNCZK原ry{8;~}ϛ݇:.n-x f'{ :d>x{7Rc?k?@@C8;;=7{A{ ~KP`d xsH=yM)={[ y{YxYx B7#T"Tt!B %,(t)<&'̹B&,B)+B-$2 *C)466C4\*$D;TC<D07=B#[џjQ?4"QJQBA4EQe3973Q==RH; $}ў46 M7/]7.m7\T (NX P i yGSS2ب@Igy 70 O E0OL?]@/pT0K%50=NC1DSGmJKLTETQ}ATWT&!N\SUUU%NW%V PRS 5MMNh]NimNj%ON]""k5Oo]:mvVBPvRw SpRd>U}eU-Vx(۔4e(4&Rm*}U4ьͳ'Q؏ЍX%YU!]RUٚ}RY)XY* RySUUUlM :%JW:Z;EXdfEZ#ڰVSMc]['U]QZ^qr[aXжPX-Tz}ν5־ۿv]wTJ~WPa\M+ygijk o%C5\aUe\u\NǨ s%>-[=[jx]#p`E[߅ĵ!3ݹrޢޣI jPe[m^ \M1X^ H*RR#_XU_$_߉uҒْ*`+%=ٙMY`_؜EfYyR5^A^U_E_na99p_d@]]-2'%V˝\VM%_e_T=&._\ &],Weȥ%W*'^]+Β]W-W5^6^7NI ,-ޥcc3~]@^<^cBncؕ?b2bC~cBb1I.cG> "p#a +! Š*8cR (R !'(eR Y¡Z^^bayb bf 0X;(8b"x5JOf5"7**b;f/J.V[5=Bag{9#-Š'h<bQ%b'*7zx ~IJ#u~Ё )%1 1HcIZH+LRzj%FbR|V}^ Mhz+0h>ij%`>B .5Ӊfr'k꥙N^A>Wդi(j楬"!)]kx꿞>jli_uŕsj&[vb_ e>FY 6`lm X 6m`_sʪll%`]hl)cm6 N픅)áPގFkيn%ҟjh1dϤfb[jfon>jΪ6xpWZ wnF"eZJ Fsi Z`.~i(oB,P,g4*ڋ^q V;-$TPSPH0Y66.-c%\ޢ 0k&+//e1ϥڡNHߒ Ne8 9Gc}.'@dDg K~obGfk(t$qJFrCtHD~{X=uQ[.6/fMoHaWaIs7'SuiT[-PuS]M e]f]`^[h]avT?׃EZjvc[i_D("Z[Z׶.bv_LqV6'ĩwJwKwLw0q({W%Xm*m~47_ mnҖxVaמoxߋnw&yߚ>wMwgWhnyp~r+&xv7pqokXccr5XW}Cx_)UzWU䳏ucb?v>6UwoG{ozaO/7uYuvovb{E{O{]{z|?-t}om*#.^sӗԧQ07fbdeg&r bpyZ)}?}O}_醦kh9h#=jW|)";5Ŀia^jnj<,9~k_"s:MSwwxMkUF&m,h „ !Ĉ'Nd"ƌ&:2  ["Lɔ+ L5QfL7mE ] 'S:y(TW# TU+ԧDmJj֯j&-v,۩o}YUnڼpmvul\{,U0`XkĄ>XݳCq3Ζ椌4U3v8FCubb 'Y {ԗYurԎ'+' 45r8 F h<˫ozgO>~翿? 6@:[g$AA&D1oF ! u4H"A[!xއ/ʈP3׍9Ҩ#$c?x A"dRƔQ Iޕ/2E<"2a Afg}o)ZC2_ o}GD^wFE*i}vx0vJ V$*j)C=ᩓ jj+gX(lVw+첿6볺F봶VK뵯f)2*~jnrklzꮥݚ.Jj֩B *JpD p_)hR*"f#a:7l#,Q2 F7PD/+@'DhHt|Jk<*JL43VuZcCr}5wY6κ 7ݞ]7ޡܝ7r3l(8~stT76vUtEu}wd#wvOܗy`XU>z攟N禷:WǮK|C}C[O'ց }>}x"Hcfӿ?f{9Pڒ/ `?[FeϞ@r0d+Aih#tBIM+$X vBΐ/Z wCi >!H F<"%2шCl"()FT"D+j^"(1nf<#È5Ql|9nQ^K# iбGG? H7 \$!HHT$xJn$$5&bݐkd@e<#`4PDB,""2|Xczcs*[Wr!k4ÈF4bx 2L7􂙺$b " A'=yS'P irشf,Mo>'9͉Nf|gO7@h8Q1FeI˄nlh8Ys1Eяzԇ>{{ΓtIR |5Jo: DYQw>%H:T|Ւ|&RaVsؼe6rRpFT7nNf~t4YD:HXgWN-"JD 6m{`0tfICeTxg. >>T}8bL!IeS-N 'և8qUYz$lek[ֈuoD@ A| w P{D S@:ӹatPۇV.;k󚷼oy 8ƥ4rD. b~K]$. ,^򢷻Χ 6j\@X?p[v|n{X.1zO ^v+n־vu>p{EF#CXTAd` 1R}x^|X=E8|_ϢniZN>čx]7h mh"Wь08:Q,0]D_pW8G[Z|2OrTsy_MjT͆TybCяHsnӣ~m TtpO^hяNı+ݯg7Gmv;G+؇w':9nDF;Yn  i b-"bb##93744/50Λ0اc;ꟷc8*9mU#+66杠=4`Di#ݙY_d$5LQ)*Z9uEc&g .0Q1LCY!8tՙH 'JSKXLԭSMBt%!! 32UYPi1#%6MQ>C*%S:EhQ%WxWUZ7!3&X"X~eR.4}[b[ۈ["]3$QYڤe`%aJabV\zf\@Z$Z%^FK:SLde"Cޭ23"yijQ].LeѦg)hE+86r٬.myg)CšuNu"v+ISxrT5" *2AW OUO?S{=]܉evg6g||]_~6“OQE炶g+9h#T'.~^hFeh悈Jb(gSڧ"ZhnFݨp%ZhPe%pTYd]IiA35V|F^xeSydQM^* VQܢB!)2oNj%Z*jnrjUѨr\ѩQ~jbjjQŪjk k룂j!2+:BJRkYjEb~+++++ƫ+֫+櫾++,,F.,6km*Ӿmɮm-f-d-^-0C؆C¬ҭ.&.n^ʭNnvmZ,Z0?.?0n꒮.Ʈ.ꢮ.N....b/n,߂-r.V^.bƭ"-oRmrƊ"؊/V>.Z-ƯkՎofZ8#.07,ێn:o^0;00 0 0 0 ǰ 0 װ 0001+0\ %/$GO1pEC/$ W1&0XD@1%11ױq$Φq  mEHE@a.#K߱$q_D@&o2EH5ԀEԀ5#;r$O$[/ 2'2-ײ-g*O2% /PB-313$1'#p34G4124_36gWk73807q8933;;[:;3=Ƴ=>/2>'100 AtBq?cDCkB3CS:_E004BwG7l&sHGD˲%HI0>I'FLϴtF(MC4Cs%804M5RϫMCgROQ0PqOw1G'Vou@9XC9WXWXcDY5ZZS8rI[5W5^R;5N35wtTOs+'SB/`5c7J?H5OOe57+sw1òd;6hvICSKr_1>6k6N,k6m{l6nv1߶nok2op7qF8*7s#D@.6t @6A4E@4A)d X4t("Ei#wF03b70È@<7%0 (X \ĀSghIwxcDy7wzc34 v@73,Bxu'@/u_n;CX PwS8c L)+4 |Vua/sQ>hOáSBw[DO_{?|BWESg$8@B @ b~2tH "A@#Xc JMq g$ RCD,'9fM7qԹgϙJm6 L@H%ĠP!WuӺkWJPJ06VgѦUm[oƕ;nݹIev SL7(J?'LTF6z\ķ$Dx* ̀PѤ# ZljX X0a/Fno߿ej](9 lxE fW\g׾{w3ڐkz&XmǗ?u׿ P ɾ LPl!Pn 1P 9+I,QLQ;aDkP1Ek+y,ŭ(xGaQɚIDR(u$('磒+0RL-DiL-J6̲-M5LJ`!ySJSC-JKFs9u7FòQL8ITQLeL(AWtQPlS$ MWX]UTCER?iL$TV}5Vi#mVaoo&k-7D@}-xGdh'LcTWWQgɥxkR>-[s8.qՁ5݆1r YrRXKYd/2yOfy>C&foa~>VZgu/{hy馏f闣Xkz꫇OtSxt^e5ԦSy^۳`ln8[a{N\F6P aaDY՛^ P_<`b%IWGՃw\ʷϽ'tY3,NpNH;ފVs};?__ӹKOx韇^©?zIrm_1C7h4@ P t!;1P1AENABP, QPl0 v#y%iM^`cD#AJ)ZXo(EXMt Bc$aIk,0F df3agAh&&jd46(++`SFJB P$(%@Z BLp=t# U2S"$!ƨ% j Hp W&1*_:&8<  -H )a$LpLH.ͬ !_fIdBLc>d"~@"V| \ +BDՠHcI@%1xKJRP gT &FrYLJPad2P5 uC|$ ImID"!sD)/R|!YiKmI6#0<0j(h (tR܎c!XoMuo UJtZX~:iULEMQ LuR8xSIKQ%C0aa$" 4Yh.Q6X~0(`I؋dZ2r"@1r  Eb E)(iS.[~ZDm7Bo^ l* <$" nLN%@ k\þeɋrnӜɪu -/&h!ӔF4I v] ~7F1 gp7a qQb-v1ga%q }tBep v3][1% YU)ؖrye19A狛l5'~yF#gt-l_QP_Mn,#6N}b]Shڱͬvᒺmu ^WcHoi^ NrtQjUխvakYC mM \׽la׺&le#vMfGզmmK{mqw;6w}nuK['N}wǛw{\woMFw}p[ 'NQ [1r7\xǽqo[#϶ HCafz ֐FF ։Pj#xnr׺k~]|fzЅ^r_dF^S ͸F6A^?MPD#pu1v}zݟ}w/[{Ov.ZGVB5\WD[85JA]!|CфZ#o4P <)_k˫z>xTu3Qg5"& ow0΅b̽; x[]}Z׮PGߵ!Pk=bי 5~. Nvү֮An OɍIM@PNap!npo`PcAݸ nkg@t 0n "  ͠ "p Ѱ mM " 0) Op kMp q qPdPQ1Q=u-CfKOQQUY1Aos9CyeIpjj U0\P|a}q1q@O^Boq 0qG1ױ7qiw]CoM bPpm{1 O=#9#5$P$Oo-\h%1&%O0ںO'֎.'$**+بZTp0&_lR&'ٌhv /唎!r&n0'"n/*s*3".'1  ֤m 1:4l0a,}M44N62٘j2/4gԏ~.5m6N999o::3ޤݾ39׶sҳֳ6;ɳ<7P>>q=>o ?S??><4 1?U@T)AS'K*B14C5tC9CCA4DEtDDMDQB&EYE=tE]4FeDgFmCo4GcFuGGG}4H/t'4HH1HtI݄I4CI4JIJJGtKuKEwdeNXTJtMgMtGItNYNE4OMtOG4Gt2MP 4P{OEK#Q'K+UP/F5SSBye,l,fQ=E7TTUŔU[UVcUVVkV_T3PWH WUXXC4UeYaUY]YYYUZ'Hݩ%SfFI5.[\gc]-\5eʵRgj\ŵ]Օ H&\^]_)a_uju^i5`^a]`)Va^-v,u`__7]AV` V,^5b_c96d5e!vdcc'cve/feEf6beh}dU266ggaVh{i͕hgjyc}ic6k[ddhifwvka6mjsbϨTX!nATZ=o9Uo3o/o+pnBP MO`T JC7R'Q+Q/7P3O7O;7L,fH p4r!uTuWuu_vUTvgviv4umBww{tsTxxxyٔBuw6xx{WyA7zwwKwJ7vw{|T|zwz͗|{SW}~4}}{wwW LȔT]7L)K+Ȍ¨UHaL, (04ɐI؃r V@u T%JFIˊ؆xGz)~PBʥVap8Wd/ Xū jj&x xILK#k)kɲ&MEd@"Y&!˛ ,]AIk/d.9!؋*Ĩeya%iq9uyy}9yy\Xd嘡'LZjbP8TgaЌ :L gM(Mv̝̀はAy!ڡDpf%Լy,@F]C1:4dA@gLlPYځ|&MǙYљ ެyڀf|%ɹB.{:o '::z:wlDCCκCҚC֚ rvzE b ȇ\fȸ1گoSb! X!{$ò1{Z; ۰磮_;Sy<5"h)&Li?q baWdp@J„e(^@1?a2ƛ> ?ɢK b%. ?{ܻN[û["Pxd' c;Rxv`&I:r?~%ZC*( $:"6J|?[s"v܇Ï&/囊ʿ~XU(qByQagXLĞvP[ J!@?{s?r|k%%>ɯ|&$~>de "|d<p'~):*SXr`Gv#()J1p\c<$3_>vh9( #'#i$=4*>c_ *mKуWc (;)&xb*"@G,~HƁUĵŋh1_(;?k6õ?r `[?_Y=$]㲙Bb@)… :|1bCֲYVC!SL0fC?0Eʕ,YRh ?Yr!F&[ 4СDXmH 9pgOJ(6"֭\z2g`˚}(ٵgӲ} -ܹ[ҽ+.޽| 8 >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;mN;8x'<8-›;t~Xލs~w^;pf~yժN"N_^yQ݁ "h&d 痁>^CfaeI C} &`I#y""p-6ށ1\)Jfvgϲ'(1!҂VdESu 0E3Jf\# CRF MX!Dԩmmo;jdP*p;T=0 hr+TJNK]tkWwkT.,"2+*mm;TNUovۈ7};TW4/v ` So+_uFu˅D o'ar8'ZVms pKY/b TU Ր-\f8 xPa|PǀABSM,T(Ky0@)k^,;v ilR8#uB2{gpQ.*/5^7ufD{&*K}DUʟ_oJsJD-xRgZַ-h67ZkM9ɇqt\Ff_q{h-Tlk;8R?iJ709"\K?f$=Huʛ Χ|/C1{4w??T'~crSv{O}WT~HW}S\ rٷ}B}~H~&r}W{bF)8Bj,G0g2v{((}'xI؃<Ȃׄ맄:ŇNTLx}`D{3ҖT;~W(UhGTpdmhI5q)fԀTPg4Wy?ǀxr\ x(Mp\}jPuHT~pC刡BB5Je'GT1reRGbqgiI6)z{FUW(zpuTwhUX8gCUtsXfdNoFҠv^hy(VWoՍu:BE芺Tq cƎxuqjZҀBՎ\mn`UWvpk0`Ǎ'PyYrplfٸ&i>Y>ZZ剻l1X xg[$rJYS& Cu[1\pqczu YwUiW[]rT9]P`a)Dfd f |_{i}ǖarɉS IyBjY`ɑIoXyC56芌Ti%&+VZ OTF HaY?WNXV8y,TPUٛmTMulřV9Vǩٜud5^Ŝՙ\UשDŝݩgIU>W≛9깞 9)iTyٟ ^Jݙ4 *Jdʡѡ ! K"J%ڡj)M-1(*5ʡ4j9 z:?*,:E AjEI*Kʤ>OQ*5JU>Q&SIzaڢcJ3zFjiZkʦ"o q*'Z1ޱ _Nwj~:jt*J:=.: -0*Yh&ꡠJ* کy* ڨ *"d($oǺ)Ьʪt Ӛ'ڬz *ꬿZڭ:QʮʭڪJ Ԛz+Zڮ  z  ;ʱ:˯k k KMȚ2K۱ڲ"˳9ˮjt5+ {!Hk#ƪM'3;;*[.,Q @} @ʫ:Zz~wuڶa*h§ot˦vx{zK| ak +>cNɑ2`JڸXU:RZOzLbG۹:[z;Gۺ:ʣDZqڻ뻫;+;ۤ˸?Pʼ^;+˽̫꽷 TKk@!T=[*0( ,@/!9QS37:=:3A34W1K +$ ?W5 5^s>s6d1 ,#A{? a>C84Ia$' #åL$3;tC+<:C s::Y+b$* S9,ŚQ;c>q o,Ȳ Q738@01)-!<ȕ43tÃ<$Lʥlkʩʫʭʯ ˱,˳L˵l˷4˹ p'eDE)&DFì ,Ll׌٬ ,Ll ,Ll -Mm ͋  -mp@:   ˀ! moPҔ59]=߀%=5}C 9=;:::~=y?s8oz/lw)jt$hr gq eo!gq$fq.cm;dlFchQ``X[YZ_W]jY`{bkajblgjpm{l~myRGBCHNPQQWtاռҼΧΒoI@??????# H*\ȰÇ#JHŋ3jȱǏ J \ȓ(S\ɲ˗0cʜI͛8s}:sg@9Pwg0 a0%:QӢrhF5Lvԣi⥽ Z Әԥ0iӛTQl)Ad*ԁEQ0ԛ65*Kqҥԧ=)UmӪ@OeQT*թcR ũ^\QWfae+^j֨j%[{ZWzUo+Y׵|kdkZV%,f . h=V&we,jYll);X^fUYֱܶ _K;Z=.c;NVR5moZַ}rAx`f9KNӽ߄/Bћ^Q$*O> \xfX>f`a68f8=w4i00| $EĺD-UJ#!&1eNWt(uI9~c wGF3dL6H;)d8#ePb9F6MىE3<5l ^&vVJ@ϐC/痜E[CAX`: d2AxZzN-GGzҕt7=L^N>A\H _8. g& :Ё@ XH gG{ B rmdlZd H;V0@FJ}:ZCn"@ 4(Aq[ u'X#wQ<"'[^sMS|r_c(CM G:K.']GsLT:Ʒ~N|+ysPbv@) V$Wr; RAK5;>n>Qm؟]H {~^!}Qmg7[sCYv=Sm|K a?{5?A :^U}0aۗ[iu0lrNjVp|}f  H Gwlv/ hFyV!g~g(؂.0284(@T28  m549Fq!eg ph@(z7BXN8G0JFL3~7t[hwTb09aY!l$_[x^wr؅ EWr[eSbS|0AEhx3_f\a8xRg3o8m8`Xbx#`JtQȅH?؊)2x2P z ӆkusg jV*PWkvnÈjxEP MpmH[lQgn~nWvVp|.Awvnnaomޖ}×zWhrYwv{.9r 9s)Wpy puu7uW׎ƷHp*,hb?7t7tvV'*0d0&0p Ӥk(yhPDxQtz'zxzaiƔ(-y,gEHzWENxw`0p80EGo7Cw}U-i{I}{Uwt0`.Q}Q7mVrxy.~B}hXa}f.)m!g r[ kfX߲HV2Sƙʹٜ!Pj(HhIIȝPHHIYd%My9H鈛H׈8 AXQĞUɉYzHHZ J ĈH1DR(Jc502:VhW^UXH58٣.@:1L*_>!nmH"jڟZ :MZp_*Uڥmj'ڥc-k Ieڤau Թi:i.)y*}ʤ:J7J ?ꤟ>J6ڪ :Zz ګ꫼ZƪzʺʚЊ:ZZ:ךܺڭzj躮:6 z뚯zگ0z99zp:+[;jZ޺9j6DP@z)ڱ.k!~[@9@ʲ*ֲ[jҴGk\1ˮ0۴PR;jZXZ<uP4+aV߶gVZItPu@fmkcж9C;F H˴poڸ몒;k˪^Kzkzak붘yJ}L+j۪k; kaۺc`:0˻nJ׋˪:0^~ z N=l~=6Z~_'뻇x!#vjW+=]KP:S  ;@B>D^F3i&dseqfJKdOf8WgQVZvS.dAKjǃ]fLf42HMnPgy|.f&aPx{犞NbQԤv;fX莞鈮"*EPz{\EZZE\]믮W]޵]\\S]Y^~쬞̮~^nþ]]]NҾNnSW~.. }DquyaGO$oncn%#4?r^3/1S~#>_@GIJ_a!JQ?aSO^CPOK]oG.@Ɋgi8xR*nuWjF}ޖȌ8!1Ai՘i&;\<}kzo.8k akHn(l\ݚz8H8C`|Gn~Wiooi|'p`sZ)rbGzOr!G v |a1g?pNeN$ AuN,TAcZ)FF=~RH%MDRJ-]SL2)xyFrAD nTA*$3ЉTV5Ydʕ-_LM7t!Wl^Zu~pد3ܼqG5]0 c>ZpƽKrYMpw+4&Gݎ N-^tv "ݿ_|1F3p|do0T)S?3@8r',O!:#\Ѓ Ȣ:oE а =G2H,9\\<m( '\H %bGtRH/O#Ɔf쨎-" K-op N#/`~1ѠLt!H# +cQG4R\t*=OE::QCRlt@ONdUI_hl)2SD N"2# XcE6aiYf6ZiZk6[m[o7\q%\sE7]ue]w߅7^y祷^{7_}r_&`" `8bMX.8B8c7c*+aG&䑎C9eM;#qegX>z>T9gy9ivd:1&:ifzD*榧j}`%4;l&lF;mfm߆;n离n;oo|]gG:`zƿ!I_RE+R^؝歌>gTcɈ܉ipE0z0ώjc C1/Vd&8IKfҒKG&Ћr>FvtJ iI3JP&>aLҙl,8N="DxKҨ)lYTy;C ԟ1:}T[IOiU>Z%9UM\;*e&o9I{bwp*ԸU#шʿ ]eh1Vld%;YVֲlf]vֳgE;ZҖִEmjUKЮֵml_Zֶ]-mq[Vnps+\״5nr\6unts[B׳E)Z.wYrEʻWm{^٭|_׽&Q1<; ekE?`O,XY@BhAr㳁 8F P,M cD7 ^p>OD> nÝ-D9qrB*.1a`#Y4("ʊ,@Dy^C'bQܸ{as6C.Wd&ɢ9[+g9z^ hΆ G'> a Y<0,d"H>1g\;6T2#OYˋ@Á`E{6Fa-Zy oML&?Ϡ3m(ͯj7pi-֚HW N7vnkQ 2bv>0yö-3mx08 !߅.ʌNܙ`Ź9ˊTp6!|CPyhqa(y t1\=x; XPg%::rBφ0gD} >,9Û0@q7|9E^ZCˮΖؼD)xY/ygut;i~vg"M[˝vqO9^Oא/lzwk.{Bɿgao_R(g_g2gms6|pM_ Ȩ~.ŘVzgq̟6CGN~ף_U9#@Ժ-EQ+,dQ2S;>Ӓ@s@‹DAr3] [A:@dL;\RcK4/ 84@D@#B":B>r=Aj[b>{9SBz34:-7Cӿ{# K D@#&t< 3=DC7*R1:GɋDi[[8#N\^B\+K!Z,A06|5R\=SD>D7kEK4nK]E/S\$B^ 7 ubLS-(\E\sfg=]il= F@"RqLr\EuxEQFiGQ1cȆtCF{FcR\@"'@|%, }L-JDFU>Dl3F?c8||\Q\q!>?PHj؆jGJJʪ6,HD0NXǣ,XBckE`Fd ƋdzLKdJt@źDl/ G,̵,L6쬷DL@KxT[ӓL9L"L LLђKEMv,Mr:{tHJc4ήJJJd<ï5o-+ʵ|MΚI_I94g{NTN8Dc7b(SAS@3E:48 U]n+abؔKe,feiV!t!# 0i]V_V '$Vc5d]feWlE;i}V(]kFC{U^S'UWEVB5PzWj5X=X{:TI=zU|5pQ~=a72MKB*`-N-vI.J` V-;`ׂ -T`Z- 6JaZaBRa/Ӣaa.b>bNba~ b,b'b* 1&12F4V5f6v7c5>8:;9=>c=@@A6CcDVEfBfGVdGIdIK0vcKNcNPncPRFcR6TTVRfVWNYZeD6z1~`e2\~a&E6ceendfAvg&di6hf>kc?2^6cmerl6ONgLFu~vvQIyc{6dn~bgqz2g^f>6h&hhg6g}N~fNhgfdfcggfqg g~vii2i隞J恞ig0j.ꤾ1~jjꮖ^ejijjjk~cjkk>k3kk&lV괆nlkĞfkl쵶ȾlΞFm&֮N>mΆmlֶm~Vlۖk}1ilNn^noNM^onF~oa. zgdehgNfOio?ehGnfVp~pf f f 'g pecqb.\>q[NY^qXnXppWWeOe[q!"qGr"or#Ge(/g%$wf5s#rrZ*]Xz_(+1s8/scodd0gg9/sAgpB_rCoFc`sDrG_o@eHOoIdK0~&M߁ r7t:uS1>cTGuUpMt1`i.і[w\߮m^vcuv]>u[oeFvkvgOvǾ͎vvӖpowoq/wV?utl'vvWvpw]wsgvvrqwڦwVt1b6'sQ/tRx/?1ftPxs?toLd7FGH6yFyd~ux癯xOtd,oyn?hyn/tonhyh?pyyzOn rihz/zoh?zs/uxy;'`fz@w{N/;n`1q~|?n||&/o_|LJ1e=}}}~;.Q}2PmmOwxk@jioios_wpw~}'~.ww~fog/"􌇇4ȃa JH m븭Ǐ9zM6 >֢e* ql^9'φ"J1 #J Cڷ M93./ ,'InC'Z 0K7Fʻcp$Āb 1IL-Drljh JPF=^[|r$)|4Z$< Љj$OG,4,kL4]{5WIZ(Ϛ[cޠu1tD21 7ЋwA^=fm<6nGyђ RJ}XՒ+֗~:[ htA+ 1Cs](3RD oA0<.=[ڶzLC{C˻.suh>&ˆj0e- °(0yCQ  W7E%x26@Oy0,h\CP=\lFvk ȼ*~1^-, 1(q|c"h%2%\$`5jFV^v7}CX3#i 8DbxY N$qر!(A}Sžv*/yNɭVtd2-W,'168Zllc B==rh,d<5b44զv=WȞ/<.~\:]AG xlY;Tq5u4QFԤ0oLV8A`-)ъDXag Fnp r*po9!NH~[UuӺ5qkuYH͝=?K&jޝp2$HIRmdn(0ab!.<%΃>- &x>ЌaX֬hGC m(+;PkYG qQR+P[]H!19c[ Gb!Uj7سdO !dL&<-`/lګYٜ[Uڅ*Պ˟B4A ~]& By˟E#nρhKeđ /`[`ry?w A>/Sֿ>sއBIo(&-wٿ<:_ϟi5oAa CyѨI `] Y` J຅p\`vbS} đ B0 F _` }   ޠ ! ޠNJMqH}$q` a .!fv!JaBCjPaKUCJ`~E! !a:U j"""Q!!2:B a!a&>(jZt%R"`* b)b+('&c-F a2`nf"gpce!'bQ"94cៀ;֣=#>>#??@@FA$B&B.$C6C>dB$DN$EVERD^Fn$GBdFv$H$H~HIzIJ.$IKdKLdLΤMz$8dM$ONN$P2O%QP%R:Q&%Qe)*0%O.T$UVM^%V$(02(1VbVKZ$[I%\DzA)dJ)B<+C)$! "䪴aΥR$'`.d`FAf9,f@Z&fΥ\2H³B+Bz+ f J9ZF/0nfczfPvon$pE'qVqO$*'*s&BTd$!B@$"lf[fd)|A@RBzAvn'Av|w.&}2fr*CJ$*H't^AB+d+e|'x:88$(Zo*J@~6n(KzLv("(AdlBPfsACdC'~ dz.@(ڨԨg%Rd)$"@hU C@A2t1gc曘͒heAB$@BT6iFiA"i cC"N' ix)蚊*Czch Yb:f0A:@444*D*ƪ@*Aj$*j"+*k6jFV+Bkb2v+B^䶚+~떪+뼊+櫻:k::A`f@Ωt:&j,⫰^l~%s-z>D,^Nl ̾lc:,,bΖb@)¬,,V퓀*Wzvd.iؚ؎hق()l>PzZj&$o:$") ~gE.A !fNÅ^nzjznh*Dnue@&4@-f6:N$"nnf-s*$:]m'x֭Vive",2%Rn"g ZnZd]>l?.,pjJ~$ޭc.**nnd@Btd> */A9,fKnR&/F2r:~BpK/Svqj0qrydSpDBzQ  % h   C 0ְsf0 0˰1 1\2y& #W1MѣgKa1iudm:5Jq1 (CyA6n"";1 (R`:0F0 ATG*R<q _)"a b,'0 8"(crZ22㕴2^2$C%FB#W$"UH+0a12"A,s-a(=W!" q)h&-83D{M5b#18E{FGYrD y4#HI4JJ4KK4LǴLIVU$(E4O z׍EtE|AśZ$qtOGX؟qXC݉E\ɡLT׬\AY͵\}k]G[ܚuiٱ%ҁi8^C4_۾y@B%\8yAQYHcu$[?W5ЅUup%ne]ܝkԝZ}VܴPnGw^?^$^mݗRK7x_M7yy7zz7H {G||OF} }w~~_F7tƀEǁGǂGǃG Gtx_Wmtss߇G߈xsxO8wxw@ ¸/贇8/Ǎ#GǎSxpyr,xB[xo8n@{8_|9\u07FD6}Ș9ywșkyrdsy¸(@5DP8Q8XPzY$Xz\zSR +3:T8z[zKG:P`śsUlD:Y\Wz;:::?NT :VDJܺ/cD{C{X:wE3{czk;sQP;{{g/y4yw8ƾ3x?'{| ? ae<'H7[yEǃGʋyw[F Oÿ~|ͧxͣ7_σы|}ҧЛ|wgԣ{gWxO׷׳OدkOtrE2 c;5sEPXZ = KP+u^)1|}8~Q>a>T@+<*ۡuaWvGB+0: #1|;xf[pþ!,Ν]LO2x sdу<VwnniH{ܝ#ysՊC';[ߛƎr+.ZAIΝMv/ezO>;jUV8p;ϡ,H9p DPAtHދB cdt[@#ɯCJNPD×V'x$Hq,ȥn'T"A)aJ̡tV![Bp7QśH1bd!6"Duo}iuW^uZ%HGu-|kL۷J:uXcKmvqc&PƏ AeåWX%٪ HQWWqfq~#簄3:fis^izک:hiS ;<]YE;6:f֛֙h ,<"9p3G!ye~eVF9.~,˅I?o,CDvi'ETgt^$sfGum]w_ѝ3=/xgyU=b6z%o?=u J_ew(Us߽{#~_ _˒_q"x!]Z/ʺ!$U4*m`* f,`SPnꛏ* > uCPNE1QnGAR%5IQR1+-! Δ^lP'U_\A5 VMT'F#5Ic6 (B)gQ`JW80}i ƙrՖdTa(Zƕ`]:Jr_k(/7RQΚ$ocEZM0x?yg b*)-sK+ĄJ7Y*k,$GXbMyT,I:o-νbG\NS7*3%h{vX;fEҍq HwXȠ~4 û>diD[gyC~mhtSioμQhHFrl%^mAB[Zgy;FiS'rz >Wsֳrwg=[W>yzw~p9N]iQ#;Ğ1*BK g´ha>ja'Zצs ;q㜆yM+~{nꢓ@ ݮrvujW[4N˔r o9kOy`#N72~U.gz'[>gJq0=RÓz5< '̧pAzя7QU'g}$\{Ϟq^{7|/_gOw~?g~/.P~Ӿ?ַ_=~p/O&!pZAV X ^WJ dȡe.07?fUʠ]8Jrp{0Wpp_ t8j'p4  p|ppQ,A9pDM0&(~* P!V 8^ZaZaY@bqkqQ7`cOWP/1WiQq ʀ/q`n1Y QqeQPqױ_1 n17q¡ 3!`"+2K#21 i]}"SV?Jcc/`Y/!!'O(_%(Srd"*/ 22),R,Y,0(,]+-@+,QAR-+/m/0#ג)aSr$u(11&W/'w*.[ 3S a a4Y511_Sp%q+go77PD3eoRy07P>1 :<:8R99.72<]f&Y8m:/S7%+j<=q5=g&cCS'H3msޠ?` bCC ZD BDQt TDStD]EMTBͳB!7s>W4ZDO4F}FKGE2+ pq7ufHIYJTHTF_QaQm7!J ~JyKKMHK/037)<5TM=NM4P_J/ۓ:sS.3961HMW/NOR1.,"CsO'RtT#uH)Ma/UQOMR/UePVmuVt@coS'J/T3!VtXqTtA[\A{2Quc/Z2g>'!7AZϒ"+U2@2?/s\gO) ʕ#5ԕ?59*u:^Y\+ʹ_^ە2r/;]bu``Z}c`t2 BY [rrr.0Qە6WS6 s ecv 6ve `v*1h 6C1:2g-x6ibjUjj_Ae 8rOuOvAvi9Wn o7R74Zu;tbfwwxW17u Ew}v9O(u{Afvqw8߷noWtO67:ww˷w q&o~ ؀Osm21874pnAJNH8o]x!cxg`ebkw{xX8xDXxxX;88x:oC-a;.ؤ: ʍ(J8X*OXNNj.3ylN~ Ҳ͝ $Eم xyaR.>\d9yyd z99jiR쌕9Yy9y9y+x z`"~a9F$fjx!|*"!Js<X8*gzbªp,t:M6"Z:j,9e%tΒ.Tz+a*̈́hFPD.+c v|Jà|.Z:T>ZljM\MdkVJN t,5z٨oEb^3Y0zlv# "J!#2l~G򧽈|jDZ1;5=A;E{I{*f:U{]ab{i{Pq;o;y;w{;{*긙;H{{{yebXv;{g;]{[{;gz ܸ\c=a-<'< •=޼~^Y>[뫛륾>cbc^^Ӿ?>~6,Vb` ~ v`Lj"Y| *"@!(:^"V~e# D"h *q@_~%P_)c'l!" !#  ƒd?"+g#†b&I%cx<8"I:d"A !#^Ŀ" X1oE:0… :|1ĉ+Z1#;(j ȅD`!D3̙4kڼ3Ν27ǐ#<2іI rHp ϭ\n :qϢ D(Bܹ^"tڽː|s_Cɽ(":\!x+[9f{Uk_ xƊ0G{*yDl]W/_ 5֯{h2pN2RE2DD$[˛?>=F %x *`cov h|.-tU`JJPXlrGD)!͗Ё~b"fEvRaB\q0E"10b1o#~AVP)0*_8ቀxAsHez C@VQ`VIffS>@`%dpWeuWC9S++xIg$A}> iNJi^ini~ jJjjj kJkފkk l4%o =Pl\VefB;d~s{Flb24rVA.wT&9SO~柏~~O*wȎ? P"{C=行!d*``Y.C-Be0DDbK!  !DT ƒm.AXT =(Bc-(JqT,jq\0qd,ψ4qll8qt p@kjsD5ˉNtA!,''&%$#%''()+ ) ' & $####"""""""!#&(+.256556667 :$B'I(N0Q6R;U7R4R+N*J(F'E'E#(E'(E+(E'(E(E'E'E(E(D"*>(-6-/2-06+3>*5F+6K,6O-9S1:Y7<`;e;aB:222222222222222222222242o7:;=>>>>????@@@@@@@@@@? H*\ȰÇ#JHŋ3jȱǏ /fIɓ(S\ɲ˗0cʜI͛8eϟ@ JѣH*8ҧPJJիX乳*O`ÊKٳ0Gb+Hnv9ݻx˷Z\}E5 ]VgA"N,7Ӿ3k̹sY<vhԒ]p.hڎ- -0 N|ab }ENvvqK6]ai 39vcו.;_ϾaWO+껥#Gzi96d yq7muc'VhMlEAGA}GP\]hA_Zw%xbxVx楍]uuhwYz0jh梌6XOȐ|Z1w$ltJb9*ꪬ&5DUm]*\6wN=+Pta|6D+aj"F+{=;ݬWh-vxbۆk^ƻYk,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8hxA -C q!$^pEtC)J A \ H2hLi6a "̴>D 08C9dX"aBA#P4b RǃR (KyʁlePH$21`#!GԤ@V DY(JSR84h-lsH\Í##ȂA@@p de,_#Ӟ=QIYӈb>}Nrz!ˁS )u @ ύz& iAfz-mR H@Bq 4gFOӧ|f3{ ˀv(na-q; u.s\-\7!R[ 6Kek ֽ@B\wnw]NW^-5 ;3cÙDm=,jׁTFMz^5̰QԖ&f)N>Te&P?xQ0a(g r. 0oZ cPi؟5SNliYEN@$;%[TAP0^nJnG0+A iGpS s[njȕY-oYaj؟_L K:e}I&4Ѓ>z"Ds5嬇in&tcFGgRz, aZu-Nvq´ĻhG[˜'Gz?>_t/⮲Mzη1҂~NO^p3![gx3s|5> ?K򕻼-5|8ss@σN @?:yN9ԣ>r!pG}^?9r#}f9~/ ~L"u_nt'2X  b`*v oL}EtҗRG}=s8xcaXBo{ݳ}9zJ<r`{nyw^|Uon=dh ȟ?[/wuuVnpnu}]HXo7]gmȁWqXG{W{(-P'~A)o5`g6oGXo5_yPKH]5oKHM8фRTZx\V(Xwa8U؅b^`fJgle8v(p(pwi؇uzoq؆tHz؇8k8X|i~艖XHȈx~rGz&*XwxXhO\\YLh˨؍{or7XoghCpXhxnovЎZ}h8y~I~z '{8}Ȃ-hqǐg7xG>dwy}q)~) ~+}-}/| y&oA@y*8 ips4`|-x5;{.p8I;WZ&y‡([ɕY 瓨p4pKvx-wbizx@{,x{[PNgy 7ǘr $'IiXsǙ)HiD(syIٓyyiɛd' ̩9PE?֜wD/`҉>H`C/oCy>[uvpU&Dɞe5S7TٟS^Dz ֠:zjڡ*":J&z(,0:  *4<8ڣ@gA:)JFZGBILN"ju?Z$H$DZj\ڥ#`b:-j| ʶf fUY[t+r[+{[+oqu D0 H۷pnkʹhu۸*YJ6; ۥ+ˤ k;G*FJ[;Kp ; ۼ{j׫= P:+⫣難۾y[*=[=K['zӿ[ZFL<, !V \ n:L/pؙǀ@"<l=v½Cm0;2|:<>@7lCE7l> uHTNHZLItB1vD$KVlUfjH^LN`^>BFn0lXjJFySbUf'GAHnXL]cVjY4]mNڼ^H!D[g`]So&eqN_f>`Q.\en*V=Y={E!Dt˺UQ[BN>_A3]P d(1 G-^1 _E_A`֎؞KnB}Te ֒b A-%N﬎i&=AVen[aY磜ئLBVhm.oQ/!C@X>Qjf^(k&,a1wƠ^ ݧ֖IkH?F<_]O~b?d_fho2vui:EQնt_9k_xoҎ]X_X_ӞFvFxUe]j_q_ng??_/?şү__ٿ0o?g`g?1@vBB >Q"C-^ĘQ#*R3IDydJ-SqL&3}ʮA=Y"Nmū4p1Jk(Cn 5eC>J(ҁzW!TSkW_v=ӅdɂE0Mñ Fpdg>کZo:uݼ{,x+a\gT=Pw@?,z3\OCūA\z63e9&Z1XK8},ck䦙~>v،Oc S)KlȐk B Q M2)L*9 ?f@(l3ѬO(OKDL$=DK%pyF!R6,H<9 <lM5\M9!; KmP@TC "=d\gu ZTivx ?e247`*$$ڂ=L畷zw ;%i uR3 ϰN0 ZZSְ̊LdyZ3^(Tہђ :Tg99b6]Z kDҫ:3vy뉾v(lUV &HYW\ !c"ռ7g{V9ïЗ/r* $898d.8ݥg LП]dƒ\;ւڸGZL|! j oEÛ']>XB0TemeᏠqrSCG! v_ܷ-7>jCS" { 00*p|M4 ]ɠH>x| HBF~O03D"(@&8A/)3`(E.0*]!)DŎyjhw4(<N,:"/yYA Q1z2i! \d#HF:ldp#;<˨&d `'S _A5{ d%1_MrўE,$#iIJfR1>1T>ZIMӖ #Â[""Ё. G9P"5FQOԥ 92 #(Fѭ 4;dA[:TB36z4@djm+<\.h i )Q&5^uJ XLa5 dIgBuda*7 iUw BWv!{Ugz bXSؿabxLx &ϔ&x#o;SeqM"aK0!an'8Nzc&$5] 9wgu`؅}4C~"f= vh ( s^s܂<)BGDzm7EU~]L)N}aU #O8:qz$!4OCfsAdы3|̝O]%V"Qu7r^9.vU'SK{N%v;'b}#u\Q;3>";0yW|5yw}7yїGIzַ_e?{Ǟ=s{w|~G>_'oO߁o~珟7sovdQ4y?/{_@tD@=<=SD @d>,A+#勂R >?AAܳAA (І!LJ<>> JlJ=<>>4O1OpFvOGqLGEw [UP{d-PO PEEO e^\Pse$P < OkF\MQ ] mgeQmuQMRz%O#R$PMR,uR%E|?(DN3eN4Nl@ N5T?=%IdϕTIS=MT<+ȼB%FBIIJ=L%@KHT;UTNIMHPeEIOJQSTTWTXIR==UJYU\mUVMU]G5`a%b5cEdUeefughijklmnopq%r5sEtUuevuwV$y{|}~؀XmՑ3@؄U؅e؆u؇؈r5X؋،؍؎؏eVd(5ٓEٔUٕeٖ$uٜٙٚٛWٟڠڡ%ڙ7%R(ڥeڦuڧ~0ګڬڭڮگ۰۱%۲5۳E۴U۵e۶u۷۸۹ۺۻۼ۽۾ۯ: ۯ<=uܙLx L-F`LU^`ݙq98Vhݥ -x`, ̥͕ ]ѕ=ݼJ L:h{ ]V X}Sm|=JqNqЂJh ؄E0ܙ8=ޥ Vޘ8:X]R(_ `hV݁q `ޕ :X^-_q_` `JF  VqhWNՂMLy" "xI7xV ~!"c: F\+> ,.0A0- CF5f} ZK㥑 nUpZG-(=U 6vX0X.ߘXe<_O_ d/>dR.de&f^[XM~98@Q]R#MɕYLg=n)V F/f'nMʅ = UWṞ T.^WN2 !ݬ$n]̦:qȂMPal>6Bc_ȂNak\ Fl4HjZ< g6n\x^N^c!~^ӝ {ޝ3vЂՊҎ]Y ̾ݪ^ ~nvgXE^vmhix>Wk-{]m_ '7GWqhqm|'= omqdh!W%griNn()7;`\*,rS<./s%+273G4W5g6w789:;('v o\zSy|xww{xR{z~us}x̧ Ox?\\s}ԏ'}yY?*}מm#8nܠQ1TӧR '*Ӡ:T.|e+Ɣ*Wl%̘2gҬi&Μ:w'РB4ElaLMѤKS6 TU.jTWz~jщ-ܔjb+Q S0!D AŘA ?& YO )XVg}J.ٳY^ AJT8kA,[eǃp8ʗ3o9EǎMQ%C٫kF+g> 8ld%y饿,)eDF8ƍ)$ *ɒAx*QNy7~$4c ((( RAHEy$I*$/_(%_zIوE9}`w|adidJQ3~JcdOD8΅r, Re1:hY)[fD)246T?kc CF +B)dRz+ Wf)z:%>)^]w|E9ںZx¹*Kz S:* Q. /k+Pn/ <0t/ k6<1[|1k1{1!<2%|2)2-21<35|393=3A 6L C+4M;tpP IC}5Yk53uARw=6e}v_wE)`6q=ā6y7}+)7~_5;8]m7[~9k9{9衋>:饛~:ꩫ:뭻n>~;;{?<O+<'ï>?_?Sܟ?oH&Ǿ },@D B)1- .Aip'.hЄ&.B;6;WݬHr/v w ގ6wCpz+̄Z4aD &$ /\L~ 3c8ot,eS'T8;d~\a'ۭbXEhؽBL&8E Gґ\GA򐷣%bg NS nъ)`P5qc*՗!ss M&B>wHC@I"-wMP9qs۫&;7Krs+~Y@2 3v_lp)ZIMQ%"h&9J -hGz I7~!MCQrd?ySJ A961mXC}˸2b5"5Rժ FƊփdMWmVnuh+\jWUnݫY0׻kW*6m_ YB/[YFֲd?{.q,`5цehQK¶= D4k KoD1lO|.SZ;Ү\iPN~X#L؟,0yYv5F+hq1l ШXMnehNٙ!SvB1ON1lLrĞp%Mx͑j!f.&%T&?xrNSi=N#ԄZqM .>Z|:Wpp3eVԤ%_mIT7ZyD_؁b:ɋ#c*MGeǑntk<1ѓ/=CԳ^o=1OĿ>W|o{=~߽cP>}i_4wO>~ꏟ/?Ϗ~Ϋ0~`Z ߀*+ `D(lDڴRV ] :n y`}M%@'_hA%4 l"<_vװ* _La1a FQYaa la[ ҁ ++a M!2`h%nZaRFa : 2AHUTa #V v@#<_a-|܂.+|"!6`ۀ XCWY+Ƣ+b,]+,FܢA5"0"-#00#t26#2Fc11c2"1*52c5~ 5c7J#6>8r3Z#:j:N8~#49c:>c9#6c@9@c<7n#=cB6ABB&,"-bE^/#F֢F£@Nd?J$9z#5>T"E?cG"CF?LAN8bZa$N"#]&vlQ*aL$BdIFe5KZdF.FcU~dSJeWfcM!)$]ґ32bZe" ¥%ҥr eO%-~MQ(^)܍!"fJfZf)Rdj&rfgv&`4B#l$_`BBkgMX_e`_ҹm~`ppgqfV`r*r" şZhb"tBMPe߭Iq!!xy^sgzJa{.{g|Va5_^rzN |gFfm'Χ߁_'.hA~hƧB߅*_߆~Rh߈_h޵(ƨv$ADͨP86))&) o("D"N)F^C)QژhCe\X  Λ)@ЩiԨ$**&.*6>*FN*F}̥L̦ĻцҙBƌZChD9";,BK /F.ED6¬Cުjd?tJ&+Kમ JkJPAhA#*k2+kj*ߴf8kZ`Bj.F`+,Ĩy]UBON4bȭ°L}hC 8hA'Dh ̬JA젶`ˮl%, jl̦ΎΎKJbJJ$6<D-̪^mf-Jʎk ю K`-++k˪mJmЊmƭҎ-J*y&,úM*6I%AڪD#LAL¹"+AV̜A8%Ԧ,ϢAʦDj2mj`.N8no FJom8. K@J-Ado>/.nzFn2A,hM`JoJnPJtaƺ2-l/f8JQjk2palG&ZpgpJwpVbצ0Ih~p%J /| jOM% o J.lZo0L[/ #F|J/Lژq_n 8k2Q`/]༱&1e7q w $N2#%k1#a$!&[_JQZ2o|MC2P#2R)#U Vb%,_EVv &rr, ̡ ZC pJf즄F\45Jd07W5kD88s4G0W3ಳ;WBsNs=x6m"r>gJD:q Y,̘nf ǟV/n-x8637kiA*At_tISgNJ/K4L?+4V/I=j<K;}z;iқO}W>7/=~{7=>>֛]?c} ;?t󟽉zk:@8C L8nƒ!F8bE:ftC`F@R)"U_*ʓaƔYC |XS312A1iSO{t(5*՛?ڄuTUjkو^2Ċ+Bj -]iN;Wnݱwu˗0ݶqV-Efe E@<QU|W=Waǖ=;[ڷƽn޿W>\1qŏDdrϡG>zuױg׾{w?|yѧW}{Ǘ?~}׿ P ,Lк(RP ) ꀔ: A QI,> q$LlaQF4jELyqG| RH ,LlR@Ud?%$ , /%L919b6+T! U1z2nj6*saE͌̕X!2 .eBnfȼ~8puť^a;;~J,e&<:ǣZ. CL͔*J4JL/S<.]XN0a]&yISrAE/Ȱ-Y;̖ xH-c!)K*;H@r B 6OhRӄY br_ABS@"zQdhG7:4y';eGJ[Q~ԥwc#%H5C\gZ)4KӐtsLWҮudgJԤVTQm8 U55TnkuLjS4S%#BQWp饾ԭ6Uq xE&>1آUo,X9elvlg?و"Uh#;ظV-*aG)Ff ;OAOIuR_K}f'%w OSjAA]0LTS-Kɉ/$Jܽw Ṷ"v) {+Ք%g}ߩ@iawo@52/U,w³Xe ,|"jC "i\ܑ<%:Amip&rk=})]IWڍ:RZRUٌ]U*1ۓMqkZS(gGC϶Ŵy}ޅ2oV+w #T*}C\T/n (,S$UMs|By_*)oN|[>yŇ~G|o:ҟNK}ªկnJm]v]>+Hmwu#!jwC]!z`xˉ ;/^=w<1ߟwYk'}9;z>7=7~Q{>w}o?;|cu_}!AR/}o~7џ~o cyϟ_/ -o 0*0%1fb(0=>0EICQUp1];p9*!eMp&֮p ~m' W  P x  b*r^4'0b:Apb 3P 0o  n FB+@*@`Ȱ Va5 "vP*P0U.Zq_1 cYq0'#tqG*-^ ^7E " ngQc_[!tD $2.0'*A r;` BnAn>` +0FO*"r"A"!r $R#)'# #u"9"9$b#W0r"q$}/BR%ǁ%sR$;%['w(ur!=2$e(G(o2'%S)3'2(r(;!J*m!%2)R*r'cR)j$R,O*y).' d̑+R--a/0 %oqױ {QG3'%Wq*3)3Ͳ(Rh/Es)3'r5R&JJ6/ 67 77.+6CG~ق> "2- usu0'@N3S Ssu1#@-Q*L9?%6A<(B:(3t 4 :A; u2d13'>#4ʰ4 A/d?FJ4?OSW5ZE4Aq(DE!GEiut6GPHHpIGSQGTFtIqI0J4DJ]KJtK4H[L4MtMٔ/2A2MtN/L LtOTO4PU@@C5QuQ!5R QR-R# ;:S=S /6ATMTQ5UUuUYU]Ua5VeuViVmVq5WuQ$ S*!20a"#8 %B yA4B[" B u>61z5. o:Xu"@ !u4Xe`!]cS6=o*:!YU!,Q 2q445>Q F""q#"babC[E ހ\ ` 87xB0.l:6av"ab:bIV%ƁZA Z[ţC 66v&1j"%b1abj vvl !$A Ac a8u/FfcVJjh!l- <QKig6lV4Ζkף ;qqql9mV ABAl`7#nov0]pgPqQBs's4nʃ-pG!3#2' haPr'3 z y){#m "\>V62"+cxdt${s0{ |B`!zC`oB9oXz#cgBb)8rVvmv`"mqC+^uwB'pq 61#v"$l_"%= X&v$Phs e a?`Va}gC,F10Rfq6h #b0Y"}xj%>UmވiiFp'}|M2 YGGپh2A ތaq-*|z}ɾ>~پ>~>~? ?!?%)-1?59@<taTY_;Ai:> j: A 8 ZAc?wr# !,*)(&''&%  !"# ##""""! $'(*.3566669"?$G&K(J(I(F(F(E'F(F(F(F,G2H5K7O9Ue;?e@AfBBfDDfGGdKI`NIZPKVQNTSRTVUWYXY\Y[dY_uXeZkZn\parelhdykYmlLimDhn=eo5hr1hr(gq#hr!it$q}0w;{=~==@@<=?DHJNPOƐNϗNˠK©GDB@??=;V6B422222222222222222222M4Y5g7>???@@@@@???DVbqł뿒òýkTSQPNMIGC@:2/3~3v4m7jU H*\ȰÇ#JHŋ3jȱLj>rɓ(S\ɲ˗0cʜI͛8<3ƒ=3IѣH*]ʴӧ" 5(aՆ\^%JTVBKٳhӪ]bH]VU.܁Y鮬 /z~È+^̸Y {«úbڥgޛd&袌6zQ!#Z%*R#?Z'jڡ^gm(pꪬʐʇ5kvDgsklBZTdVk؂-GomԴyk.~;~ Xk,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡wl@ !HL&:E|H*N1V̢D,r`H2vьhLǨ6n#(:v!M(E=K# iDA0$"E#$#YIRҌC>Da$%)iQ|c*U# Zֲ K@ pyD` 8/K ²[h)&0! ILNqpb"(B(Nr1iu|gix3Al5K$.S  yq5h,y@l4& K0"B7PP "BIrg"G/mKc:ęT7d>%JG QD5BsSRFDz͑F2Qb>l X:V Ѧ9EMJSԭ/"3{%ҩC zpԡNK5k6q _:ĽATz+%f7+D ngA ~v -jKD $}hc ؒlIКmkv-l[&m\wt\؝.+ .wn]y+׽ol ri'i@p4l_ X.~3_6x)!I!G`,K:s32\L*9A\A6LA[5|lz̉QX&j YMZӓJLRYLQjVs f}^^ "nUAT5#"5Qp[odW9mDמgϸm'V{vmNⷿ5[i 9ozǻYo~ߣ/9pR<7^ᎄ8+Nqۇ7I6m<+B| O@Tr-?yE\1Gk#!  qUGG:q|#+as\Wg:.+D E':[=PϞ;NgIX]I?{55?醇aQ`ݻ8X>n*o;yӇ~=[`zחYϰ{]h8s~=K؏Ov;K؏}[,߻}@yh>/gsƻy38Xa  HA H0׀Htu@qYGP 3Xt/dY4(Vt HAftD&cR@(7 X"7r$DE8^grȄ0~z[H+h`@Vxc@exh(jx@e8x<r8tXvxxhsy|؇~8x8rx؈XH~؉r oH艤8XHxvx艴XxhVHr&wHr~}狸؋8X(HxڸݘȄ 0YwYsȍꘇ؎wX8hXD8(xGpQȐ萌)I i 8Hrx# hȒIȑ,H'm}{{@rZgD\GZFYL S)|RY[TWyNJٕSMqbYJ)VyjdL ZmnritfIlqys{}izxYqpYz )\|Yi?i{AzCٖB) YٚIy)U隑yzIry ЁXϙ2yYI94ٍݩyṋAwA795(H|Ȟψͨ˜XɟHi{h˨xȠpHȡ (#JH*&z+-/*/*H#)ڡ1j裫2ڢBG:&!ڣDRPjS*mqML[ڥ=8[uMpJÖY+P:C`oKuzy*=lWH 6n zaxJ LpKJ Ldj:̦QP}mpZm \:JAJ$B:M :yJHzɪ :0y+GMpazJ`j &8cIf7jLq $ \z Kݺ V  ! #/+Z3hqjB+Dʳ7t[Ԫ]z:ʱSkU{-;;˵g 0B[T\ Ш5[@|B+ G5)B Ǹ+J`}r+@H˷}~qK0>qK5cq:`K:gс-k;II[:^5t)kK n˭[P&˱{K$;q"';KZFǓ*ګګiJ(KQJ4t@ P ,(KD,Ȼ /*\!i:0k!Tr} `'[$kH zkjCk :\KC˩8\*1,JęzR0I0l6<ZK;u:Jbچegjlnpa dSȾ~,@ _7ܡs wȈl@ɢ8,N?,?͉~#wɤ\ʦ|ʨʪʬʮLi\2\mÁP MV #ȼk3<aѻM{{ üq * c׌e5RT;V߼]Yb0E`P4Xx۹} Vr;+P 3ˣ@3 #0 IHR #NMcM]M_]7ZyPIvSvwCo-v׷|Gntx|a/x]vWm-c`'FI`u |9~=+~TWIAyk|~Ň}ԋ-ءv~g|CuَMr=|0ݻ|8qFf0|uwʍYݚ5=V}aH'"ٜw 턢)ɸ'=Эmm߶}M=z~HqM/^{ 6 w~]޿B~s=޹m|ۥ}ۼ-7mwֶ9ٯ =2-/{//aq"Il}}PBx]uY~1^]nD.vWĝ[ހqs7Rsx=zc>肮 ~舞芾n>^N~难^駞ꪾ褾ꮞ>>蛰p.~>^cc~.~^>p~p>q  hh@n`>  _ `^ ^P `/T R1fp?rjn`05>6 c _#/'+O- ?8:>oa?T@/@N"Ro(*\^O`;=//joWc $?h{}2OfOd<>p./ O /_nv_TZ/c@tt?{ ^艰09op1F1ԣgAr$Qĉy$0Nc8Jg&j4RG`DŽ RO`*sLΝB3e1nQ#HcH1YuJ.pL1e*gН:D;ЩGMrdɓi2&Edt'1U-?MƩ{2U{M9ZƎ9\mƝ[7Ź,0">\ kh眞3Gr&uܝݲvݽsw贚LZ!/~ #>.]%Ÿ_~r GZ%#* ;Ȳ7 .AH/$p4KM}&j$ŗd5 dAL$EElqFj _#hA]5K4"%y%Oe+MJu1!HkM}iد[dSwr#Bw ua&k1 _Ŷ_mp=nXM32c*4"d!`sf3uEw~yDfzAgͩ+yW}_,mhLjFS_x+ ,R٧aUVZڙMxf/q[nHScS`ͭa' 77sӐČ 7:c Ƒ x)5|Z4y} }Fxv]z*8kGcvߝ^ qH)Bh;Sv? "X6idALҍC')Ё`AӥN{+8Ł|_S6fPt|(AІ. gCⰅ:t a8/#rH5*x 0[H Rf].WHtPFmxWYX,LӜU6mfj-g cY| n p@^4Af RwnɃ? f0pDZH H>L4z0H~(t-_YiO̰0Gf Q8m^)F4>q)cTZ#YX DXtbը~ѐE&EQżjBLRUaQ!jXew֖cEs67a*P*ĠB_Ua>YrֳHPj]Q.drT=} ?2D&0M2"x+!og=uԥ1 #!ύ˒$uUf.{*,L]6H`avleϺه-;5]s;SMqk~ylv*=ozwz˛xpG8 p7 wx%W5^o Epv;#GOr{-_s `5qr=ȁ[;xp7 ǛA/us8#u1v`͆eN#w-wX{mN3w#/qO򑿼3 8 ?}S߽.[/}K7?ǿ~G~?ڷv?kþL|?@@$ > \ ľS@ $ ?@,t4ADLAdT$" BD@&L=s7#:,Lכ;.;/,;0;1:2:3:vBCë{êCçCC-ñC6ahqBpؾ?<9F9G:E\DI;J 8H9L;KT9ODP7M;QES;T49UDVt;N8W7R9Y<[9\E]B^<_\<[U`FZl9db4F_TƔ{FFXEfFl,k:mL˸l˪LBlLdLȔBãB;ɀTIIț,͜I5T;IҔœ,dEڔ=Ӝ͡-A<`0Da I4ItIDEܤ=pE,Qc|M*KQ;=PQPXtRQP={Iw#pͬQQڋSٻ44>5=5J,L3-S8LµmA3͏4TEM6I]>KImSLmT?LF GTTS]SNTTMERU&\XMUV?WLUU\=\TCSQMT]}Y^=Û7k!Mg=%,U}XCH %Ri}Pp"OjE@l;0-DgVhMsż$QkW%WWWͳWx|5ŏ#>ЁU} U؇P؇_`֏' #h^}\@%M`BB]`U`U0սU8=GxEUDx}hh"͏>(`X\.֏bΏ+cPb!b#FbPb&vbUcUh^7&Y%9>^<@CFDV}ޏFFcc9>^>e-އ__X}hWΏ36c҅dIdd?dL^mdkM]Ώ_f\d%DCă`ijFvew[PvdxxEx7*v7(v(gvvkew_wk}qV`w+hvZg 0g|hrsffxwEhqs~7+gыfvJdUXh"~Wd(QN_qO~ `/1&`YfᧆZV4!\dƏfa?^Fޕv뷖fGŏ4v͏JΏ_`d&QVޏ\2_Wnľ2VZa?kk&k:룭aq=[l|U~m7Uh.>޳]Wx^X?؄Տ5vnt"*nq9]fae\^ 2\΍N`҅n}o>Z?LaAޏ0in7GpZnu`] '7GWgwq'`Pax "7#Gr7uN&w'rB"+B,O٣a/1E>6s#sG^78q;p9=>?;ďA'B7CGDWEgFwGHIJKLMN_PuHo76Cp[^ۭ^=۴kU]].V~ȝʝv LlWp<]uy;h^n#ivi~[/o{.{o7gu,}+gO|~|mo<v"H`"l 6ttf[׏V:^E-bW쨆j&poR,Av̶]V۴mygKlzkvپE^lu+lOp(nZxuFncoopotU.UX2@?Rh"}< U+4!FWl%̘2g\͜:w'РB-j(ҤJ2mL8HrլZr+ذbǒ-k,ڴjײm-ܸrҭk.޼z/.l0Ċ3n1Ȓ'SV+*sNhax:.mU'|Ԙ),{%ګsX{پv=iq߮Ȼq).:wg }7/|1_Ŀo^{Wwޮf7 nWY$}ͷ`Ah[sQ8\z.Yak)"Q! 0_%7agc!,65#|H_\B:$5UZ SZyeTZeBZ~`$fXe^Ik&g9gpfr%R~ zvI(^$(>c ֗#s F $QBgdD:WarqGi>jx*i|:z(_w28鏰߭(al-٬JX,zs-;ZkNRF(n.F]kY?/// <0|0 + ;ê@<1[|1kqs1! #|22-1ˬ5|17\s;r? rL=44/4?54L]5S;"X#}IcsLwMs\6{D8J-8EClmCH#?ÐD7&yŎ>?\z[z QPa1R8#?vDr4l:LC?Ӈ\J74 4D!ݻ WzU4| %;2D_L]O~@Os<}|3D"A 4 hpn[Ch`amCB0- o&(lb\=M Zh11~C0/CȦ@" H 1z?6pP|? HA 8x!l8|#h+J}T6h4C!:]a{@UlXjH"IF~Ljй,:l HІ.OZԇ5Ŕ(RaGALJ8J!3V<Վm[8,R9|":a7 #5?嵔|^XOFO|5  {xX^@9/y'dCѾ-4 =>aHDFVX /bbN&o7ԢP& ntӂow|Vg6qz^6 mM{F;Jd Z,o ﻍm!n 6nx/:-soՆBù5phAL=c[#=:/+چ]L2k qc8񏁼9Xu40wRKvG)3y01 *7U20  ;3Ӭfաk~3\6AF0q: ~ȟg>S$Ѐvh~ ц4 MCO:(n4Ah{$ LZq4U:ZUmMú@A'JZ8rpԘ.vBpMV3ٍ5+Mkhִ} mjS{@pC8RW[N{u{'x7-yû/ݞvme'67#=Arc0p/%pk8Y2 7 11rO/x-qc\/xa_3k> D;ҚmmrN =b[;k-s8>j|>;ӟ7ZӽT=X;ewܷmv鷪eMv Ҝn"s C=܃?A A|@@&x@x  @eh^F e`A ]hD &]!D&D?_ BY  \"!V#A#_B?T$0^'A! ~A&DEN *C\ DP)֢-6ݢ."//"00#1o1&2.#363>2j4N#5V5&4^6n#7c6v#8c5~89b9:~::;ƣ6£.$C֣C>ds `')gte@'tb'wNgvmXu6'kyfk'pwe fIhf&}tv''sƧ}gwg~g|*rg~:xNz^h{§̧hs(D(sZhbbeW$O2cReFeb*dZc6e.dNd @(8")5hQ2P:MBiMJLRiEʄh\FrdFZiKzJI)IiH*Q)4@$A3C)CiBH)Si4jhK jU"n(j@2jY:BW:6$>VJjB d$Ni6d=*D*d1檮X"dcckc+c+6 8!iD& @@&+9<0=Zl!` j+ZD=@kZDa-lEfv+B`ø+_ "+RĻ~klZ@L"l*^=Xb,izBD" ! ED"bE*D-a`BpBBն:& ^ܾ.m.--EmZ [`-nBؾR. &-E!!4l N??|LF "V&Dޡ*Պ@.Rn[@@n-[ o @$ /Z "j..V&J.o-/LnoB]ߥP.Vڬjo n EȾSDo&EE_ ::/ ,CdZlp D{Eܮ/pE#` 6 SA@7/ ¯pWp`KD1 ?DǞo\Ff`l  @Fa~.bjPa;galqZ\1"\f1α?1W1?l<sa /BS+e\˱fP QE&1&F*0rj+Ue"orj@?X+Ha솯0-E@>o+*Eԡ33" @P3V5E6C*(q2W 63 3sW7/3'D6.<+s|}E߇$4kY {eL~W|*'苼?}Z~-cHϏ}ֻ=y w{ }iw诟ʊ8Z4@~/*P?Zl?O8uds|=ױ?HE<?E @`A4 B!F8qaCfTXТ3R9Ď5VQǖ+KƔ9fM7U$IC&$W%"ǟ 93O@%C@Ljy !tB-z~5VG6TSQtR YuVY@5TQ?G}]Wc%!%Y 0 C\aT!Xj哆Zm CuWhUW]-XZuCl7 MW]@PQFqTz}߬B9CuPMמxw cɷ's;l}ykD]h]g>jt*a(Tmj8 %ǥ/LB& V=zP?vC)Zx.N MXM~8 c.M0b b4#jJ 6C@RPxE@y\`B/PH =Zi@<k F('r">aG P(yd% rFZ$ G'nPDB Ф$)IWvR|%'4a]@PB#V'cYKcҒD҂z;eָ*9әl2LuEs9ŗI(P;LwO\^- J id7 NDK[L0 OdhA/$YhG+:Knԣ$g 1P@1tBzA!>r4iS|0 9r@1jcd3ɷqÜ59|Pk@Ay>Ћ>;7zA.?F9uot9[t&W;ўv _Ƚ# An}_s<~7o^w!yO1yoAzя7=AD~".H `!a~- "XB=a!@l;/B9U?< p(_<A. >ۥTZ ZoO @HN/>0c/=@_HI@t  / "pۦN钮 P . N Lp K4_0#P#P ZԐ Z*p0! h! ! lPƁTO%q O@ )T #)aqR4`"\/q?R1q1q17B0q1űq?O.qQqǑqq Q W!R !O!!2!#" r""-12#ձ$M HAƁ#r#QUr%ױ%]c ar&ű&m2sqr'$>R''2r)=)rr*E**!&r6$R&Dz#,,ײ,r,߲+r)=2H1R,R'&//+ *s*3'R3T`2 5B22 .3 0S34sN49 <#, 0s2W3;34c6MS6ks4c7k0R 553B7Ua3A668s6Qs5{4":Ys8Ks9S:99o3;!25:I837;73>s:2;Փ>ӓ=s7>939 ;=?3?A=3A)TBs@T5:-BAsDS3&P/)S0W [ _c4)k.gT2#OqP4GgFH}HwISHIIJ3N 10HTKTImKgLcRL]LWLQM2 qKMQNRNiNN1OOմ!4JTON" u"5""uQ OTOOR%RRRTS3S SCSGKTQ5UUuUYUD R#O"Oo!LUi.mWbW!WY! (`!$!Vo5W"Q[{| ġ @`[ o Xa"b `ZU v^^# a baU> WvV$e  yfD("ffh4 vdv$>!v!P?ReIBPgs6RdBkkkoph6 `*a@ 밖$"@Ҷm Ac1j2D@ 9" `  plM P wqWB$rI7' =h"A @Z @\k5sWq3rtP#L07?jcA&v_;Qz5,q$zz &6V@ hygB}{o#y@c'bk=f}7W$CQa | "$abAl~!ab#`k dG~qXC fp^!ba̖ Wf%yU *AZ n-Lw"؅yxE]gx8x8xɸcJYOUAa2!BY\UXѸ8؂jOv1b ~/" _Ea_wa1(9so 9[ |VzC%7T1Ypcpfqd6Oڼ6^$Py_Q 9# YwkB]!rxs50@y :5/#R`VVy~}ה9O6Z919@ZйY"X yVY|tVKe嘎u83%:xU9zI@ԸQ:UzY]a:eziAqVW{V[UY "XEX_6"` OoYZrY[ H]>`@NcUa^sUc=_֙}^F!9iou;a "LYzc;VV[MV>4#7xkpgf}`{g۠˘Ug] Bie{!4["`{f R>`b/ "oB mxoka zٰe'mEoiYoykwyzك V@E{WpVέwUC5wtpx-EZ!#YGu8±+\ ]PuM7# A2;q5yW |Y|x3x>! yk֙$7z9Y{ [$ |>R skU!78Aрջ!bYy!~I'qw /Yy~z}cO v<*.YG.-*X>287IA Ǚ@|> in瓮7<_5s->A^@>9正jꝞ . łS~j8^c^:~c>ގ4ZN~ ?oآ`"&X*/ߠ2? lՔ-5ANRVЄdC mZrH~+C?\AT,^:`&,Lye}[W( nzNh[\SfQ%A\HBh %}?vS@"  gQAU R{-1ƍ 2ȑ$G~,2ʕO| 3&A NC| "̹È4%*3iˁ|dS">b4 qZ*yr@C4h(D.b%z/⏸_Y7^Uv ڑ3N_.>blN bq *,9gѤG>Cm:Ӂ'_|93nQ<ٔ kcϦQhݽwEmwΥg Aę#di۷n43ii ҐB jQoi$߀ht&K}E~RQw\p%~i?C=B7a ic7 kFk^I;yDOxA)T]XZe`KuIS ek&pZIgx.EV+yb#yEeWtriwΉ瞄P)sک禒v)yB:ieJjijΪꥨ 맬z*q ypGk!򶡮 쮯NK,S!Ά,AOv+ k Jע;zyǑ41؂1žQWPQp}Ƈuq]-l QLfshv (\[%w*kQ7XI2AȠ{iѱTȥtR'!D#je\/GM`$vUUAmruipkU0ITk}EssMCwo3mEaFTZM l-F 0 $8ߡي-zJ'1~+gmd*]j&Ƥ^=X+W;‡&D E7qcĖ_Pq?{m7AL "/e0|]Hn?"?,!(Z_釗1l\+x fD`V$YwBP+_ {w3,] CVBP%74[C1jCtZqC%~9l(JqT,jq\0qd,ψ4qh8qtx6 |>A¸ =*2zL"E楑FQr/t$$- PRd'Inl+X|+†p@E B$INf2d'Sydp%1IJSnRԬ& iґƌf7 pn&-d wI8i~De3 mG$&? Ѐ.S<щg t B!tR$z[ AҔ ԟdi='y4JouԜT@q#wAA:TkFMRzԤ.5/M*T:UH]*Wլ5]UVpլe]+U Vk\Qֆl}[WfҝA#S5(-C+Jv.bNv& ȊR>I΂,g>V}mZ}oٲM!pdN:7{n*+욑cY,u[qtm{w}w x.+x n Kxo`%pOn3.OlIAw BxQz/.oqlËuFs52$O I4iNe,*iɲpRށ^^رen ZY dP_1 h QE=~FM`" 7DȰ0( i+3 PzԤ.OTzլn_ XzִuMy\z׼u_ {.6d+^fC{Ԏecg{Ӗ6]lo{ܷMtn7+ o`{̦}oz;7w>n#, p,w~C8+nqgc<8Ǒ[خO|,o_|4o|< }D/я+}LoӟK}Tկk}\׿}d/ώ}lo}t}| ~/<)a#o .0x/oEI<10%H/ԫ<ɠD ~K/=1~`2 !,  !$$#"$''''((*+ , -2556553/)% "$ %*'3(<)B*F*K)M,L0L1K2K$4J&4I(5J*6M,6P,5P,5P,5P*3O'0L!*G(F(F(F(E(E(E(E(E(E(E(E(D#)?)/3,2//5,4:(7;+;>,@C0DI-FL*HO*MS*QW0SY4OW=MWBOUGSUNYVRdV\eV^dV^`V][V\XP]VJ]SI^QH_MIbHFeEEfDDfBBf@@f>>f<@?Jzkqjonkuozg]XRNGB@@?><;;;;>?=:}>{AxCtCp{Cmxrɓ(S\ɲ˗0cʜI͛8<3ƒ=3IѣH*]ʴӧ"= 5(aբWI&:UծAKٳhӪ]K6T[f8w`:2*ĺtRÈ+^̸M a s*׽ W`~--층װc˞r[7j7\Ҙޖj%u:ϥN5qikν; T|w֐/W/={8ro~?us;ށ& 4{=w]`VE}ݧ^yFrx͇&"U9~݇4h8fhwe1n ݉}(Pʙ8d(fէ`\v`uטoc!olba\wXgdVy!.W|f8e`6裐5an^q"j5)~*h|g@9k O$PU^r JNӢ(7ȋ HE*Qy4'#D(x@LJ5qXjӜԦ8)kJu B}Qb5Cu*Rԥ&OTJթZ5NjWժnPUYյZ5\ZֶfUs%+^W5}E_Wֵw [X>y+aXF\f/)B6^- ڴ,GG RԵjO:[f,gU{V-puKɲ5իgzSsyWMv@Ӊjp+`ҔQDA{^ӽgTo5L" %/DBվp0KD}KF<f6̀0EOv%U:b1ؒ'piH-+f9c7q˹+x-~d}/|2nQ|2~o |Dys hG <9x#b(PA_d@EdG@˟| Nwh>޳0?DfE΋}dY={/@v" Txw"(tKA|c||D~E;WH|Xwǀ)D  oT!eO4cE ;P]4{RtsgB@uȇ|C{Gytgt7Axx7x:UDZzEIA95*HQWD !(-J , .Z̔^ E{iIzLV^~Y ٟYxngbhvt/ ѝ\d ߙrʦɦB4bSMYHo膃hytxkUP j6fI|DYKm98XHFIG諽k@YzȚʺ3g1^͊@o5hj@Nh&݁ZQJ꺮)O :z"j RT) f ۰@~0[ ۱ "[&{(k$,.++2;24{8 9<۱6۳@?D;C[H G[ R˱MKOS[{Z~~`bKe+b_{P˶j rs[Px{~@ q@vp  `x@ K 0۹ H+@;v Kp~; v` PǛ~P +ຬk+;;[+KƋ; {뷯+`{ K ћk KK zK [ۺ [K [lۼ\z<[ 2 `<  &<0˛ + 2 [$~Ɂ<~` Ħ@Ȋ*,~`ɚܱ+ + ¸˔ ~ak,kʨʊȮL{˼˿*l+ KLlL<ɕܬ˻̻\,\ȫLܱ϶L~=\{֛i\L|]lmnn,2"Ͷ$ mລ ~Ӽ1]4= |q0T~Ʉ|\Fm0ΰ `+˘l[ְ` ]e=ѯO; \+{ v0ը(-yה}ݱM *\䛿ZЍ]{;j]k-PنфM =ڥ-Ž ڒږ ~]ٵɼv٦==;xm pP~`A-&ݫI-mm9;UW]a]ҋzɮ{z| Φ~@{`;VL G\ P ÷MK &,*^U#^'M,|ħxKk+ F.(A.&[M~P}];NX޾!_&EGaN~炛);Lz ~8;+ 5ɂ޶[> p @VO>ᮋ d Mn}{pN^~Zq}ɦ^޹~M۶F‰3Q^z_[@gN~>z. ~[^|.n ﷀ>^˞(콮~n.N?N>^I3P2H?LVQOR ȓ|6 g!pCTVhtik=F!QSHI0)#3%Ot,HPz ʓ$Ē s0D̬RH|DkQq"tIF2*-I%435-D6%j✳NIl0)nnGگ3 ;z[& wpij[\/$r/<7s?=tG\sOG=uQ7}u_cvg=we߽w?w{~xk/xeBѓ_>ӡzЩ>ͱ׾{>{ï~|7|W-Ԧ˛D3o_}}_ ; @-{ 0~sUA 8x#4!R8<{\>Q ~υZCPw;!y(F cx^+|b h)!b}-S" DYЉ$p<~idcߨF6rphGqnH?1$!iCD5}Td"%iLh$'%Cv4%&7JV,++ BR*u<~R*IIP2&WD&*eYeRҬf3Kjb|4pR&1ǩNZә'35xZs|f9I{22lb,v<&|(#z0~eDQq4mH7ۑ&m JYڀ6INMs{z. ڽ*{j ԩR*j`(լrjձnZzg=#\i*׵J zWuVk^ؽ 3lbVֱp= سFx_ZXRhqY}- S7d_ٶGnu[Jx ^pV,rk 2wmtu+bUbUйS+芑 :[6YH)֗X@5g_Wm%t{1&?`y\(#a_D'3y Sg N AOS&n6S\a8(g?q{ VyE>r'Gy OTPye>s7yuq|?zЅ>tG7 V 7Ozԥ>u\zֵuw_yvgG{<>X{>w2n|w|?xG|x7|%?yW|5yw}E?zbO>ڀ{e? z+P! -Bp4cH=wBh",H)O *DXH,E"SB}_7BG~S0 cK?;GPtЄ@9 lY0 SؽdYH3`= 13CMX qO A.\@ K ľ[Id8S S@$9 IA/DAY <,̐_ 30tGG(<=.HiNd1 aA A0ĐH؁ فHHLEA Q%DQ(_?\@B̐CLELXLEO@ DLDrtT !(x|GfaF@ F^ j8dT8jF G 9)E{cKF>iIfU$_@9ʡFqCGwH00IAGAA\I͑wy dI)I8Ixʓ8$H%qxtYMp$9ƊtKEA=ƈ1 9Fo S yMDNHXD> qvSTL˸ˇ<>47 G<|֜TBݫ_HLEG1Ll oLĐ(d`L!AEn | N LH*dJڴM+Y<8CI LVZ$O1KM89tl<+ V8S( QEeE`> IɯQ>KD>ہG G!E\?jh? !PŐ5M:DQ4$Y8P Q;"5R[SG9wK#()*+,-./01%253Qӆ{4u7ә!:;95<>ӂSS т?%B5zCUEeTs˻FHTOJKTBUNOPQ%R5SETUUeVuWX5'Q*զ.})uT%IQ=e@4ÐP1\[ݞiE+'bInVɐhjUKUn8=LSvd?!}  &=q o%W ֓sN00(B.B+S\d7BlEgR}*tWl=5j-ّEYYj:Vm١Ys-ٖ8 4 LH0X e T )B&Dl$,Q-W=Y٫%&h }MKتMؚrZV}!ڻ۔4Y}ۉ# iŠE~E}v Gt\P,dQ[r۬]Y{X| DH4H4D~HGXĴU[.۹ܔXu[ٚZj/۬}]#ȈSL= PΩ m>#9-Paϕ,Q<Ҷ5⾭FYYBf-s]a>WVf8gPZfQ Q|tat5QZp=XffU_wf8vn7zx%]&&6FVfv[ F{hO莞8}hv)FF>8JSv}4y陦ixcF鯩J&6FVfjꨆdꪶꮾj&kFVk>vv붆빦k묖&l!VNvlnǖ쪎ɶl(˖N4XV.6m|fOO6mԦmޞk&TTH5xj#H_6gH~^>okSnun~j"X4j"I"#X@uxV"(p7xNhm(llvp' p~U65X[o&nnFxL8N#xjDžqg7SqXxXh#Ȅ!îFp7p.r¶0Gl217sNsST^4քBxBЄvq#(9s{gz{w6xz5xwyg~x(v>iqVn''z'1w.tYoX}WUGtgu(j`o!7>k߇+G_~"vV7sGq/r~5/4?3SX~@?^j/VM~jMFPz4P 2l!Ĉ Ih"ƌR0@< T<(PH&P %hb:wLB&ͤJbD)ԧNR*֬jcJjԞ )" SQt )XdK-Cr kw)Ê"3]m\Åō[sĞuZL UԪ5^aײĞv4l߽-4q]μYKJ:nW;k=^~6ӫg08{O?W&uV!6H7!Zx!j!z!!8"%x")"-"18#5x#9#=#A 9$EH'9}M:$QJ9%UZy%?Α hDa9&ey&iYd's=k9'uy'y/6]9(z(**c'hs=.:)Zz)fI ĩ):*J! $*:G8++ ;,{,*,:,J;-Z{-r-&-;.{.~.n;/+/ <0|# 30K˱!w*2|( 2+b̢ F򑁜$}my_q$AoiEL/)T& %"?yLzdKbR"8u= x7N 9NuyX+Nnit-;uLb3($:ru Qr~Tp[ܖHGHH'QoYfYr&0p5-M v=5O?WT+`tK"ⒽhBm̵ӾmUo]mWֵUeٸZufYYׂƵ\Wծ({kנ ga*5!Oع:իd3lq,hC+ђڦ6r$}-lNxJ-ns"#-p+\Q0\iͤJwýfۉO胵u^Rw+y7 R9{+WM>>03~0#, S03 sxYj5ԇ bBxP%&0$=nZ&S@'Z㚍_fleӉ8uH_+rrc+YLd!H)xC[?\| |ft"@$0/P}A|'M /#] ]}*="AmK uT/޽"PZϣ8\' tfv^WsnKu.IxxIynr~'Xl{.{.|||6}''zFw(VFJ&~q,(FN(V^(fn(v~((f(蝨kbjL5 r I Adzd~>&c:ߐ(L&2铺hh2ii(%i.ؙ\IJ\^>uG*Hb)բhY)b GbH6H)v IڱmNܛE[j`&HߡnuɥVꓸ): 檯)+␲*,h:ڢ!"ڍݞ܏jH>GZIJ.^+ )kfBkBN),"dRzQC+A!_2JL>k>j2tVJ~:S}j!~, mg, ,glN}ҬIzqm` ,mNbj(j.rX2**T,mF梒d-hbJ/"m*`' @mV֩.傮FjV)Bߎk+,vd2Bh0əcG*hj9 t" rkc~:oz/oNT/oojo¢¯%5W!bBbRUf+m|o 0(08p@0&V_0go0w00  jW 0 v 6yW 0 _ְ0惣Τ4qq~0'CW<1g/1pwq z ݌111DZ1k'y$T1w1SDq~}2}|rw\CDnHE4#C$'oHr%sD$F&kD`r'+H(o"G)cDžBO@*E*ĂdP/ceBAmX25r--+P3C@@8p_PJEP`G55"C1fԨ1YGA&˗N#; L63j LFRQʦGU$ ЍG&R`˗1ά)PKԠ2t2 Q:Ft>SU 4lYw&kֈ3jjHjVq~R.,X[Sp&+qg]+flaJDϯ3M;zcNԪBA 7A%L8u=+]xCF7TiV=θٷ']|~7cm[DOP}|P{3O: 4[|0%Ԏ C4ܐC*C:ѳM}jfEFR9Cc AC (G,BL')IXޡmxipvZd5n f1% >h Q@t`Մ{@{ܚ 9D D'(twMt"5HPҒ%OyH\2$CI[Br$+;]Rd%,YLakz1LNt.Dj|-b-e2dg/ bOƺbU«TX`$O.…|e% wR 5iIL[Rs56 |x5&p+)NZolO~^,& Fj )6W,#t$p`*uxkZ꽨no!D -kI :Bk >*,$hnkJ>>k5Eglg0E O~k@ 4<5mTeهjZ}= g; Z6YMYm=jGr}mڴempZnp,q)p5s!g.m;.r[5yћ^ICA2NΗ:Fy"cR6`yԢ/nё["$ε#ёGSC6YS)I ;CUޯ=v:$NF] ƅ@Wـ~×-(wWiA] qgNFs|E*a18ۑbr]}7$u(:Oy3D{Hȩy2N@cpƅk|Ed-xLh,Pҁ\r!a>bvYjgF~hHkz Ob_bHb@rTнc.J&N1Ͻfg\O']' ϗ-GgzV胤[H/5tKƳ7#Ui/3Oi(ݟ EQ 3$ch< NP#oԢ# Ro!4̲QN llѨp@R0hF .JN0$+lC#f.霮鄅EID8O'fdI(.p e 0 p 0 p ɰ pJ Fdl0,M ͸dJRլO0q=B@dߢ#ඎ9P$`$#F1:HvnZn;˭<H.-dDEE1p 1chop Gb$f^1q1q2 bNk c@D lxw'B#.$;B!+uxSE'NA/qG2'P3qP(i6,96 S CPޫ,Y.Y8oc.*P~!/Ms,iъ'8IHDONh#;#&`E% Oi> ۓ3s\0,R11ŐG // 3%0$>K%N?Ab1/C.ARl1*`d$3$[02 7#k0&#A%DK1~S'#$&047!tLtMٴMM4NtNNNT ?d@pH4(wtPuܐ[OcQm% uꮞQ-aRQOF/S7$<TMRjVtlDT]OSElDkUm@JVAO;btV5XuXXX5YuYsFDDEZ:;ZJU[F#YU\u8H]ӵ=֕]5>Au[dH FT]_C A!h#b!xr <^DbbD6BaqR #aCd7d%%&RccV4!bv:"b0l 4FfaA"A$R : `j V L`-N!(B 5+afKVjVmm60*C V k" Vlo1)@a n ov"5 h[#m*Vg bA bcWl]#2l$iiSwi-IZHLbҀn/B ! "vB^^ݶ_?dlsmC'rm"pWw!"ww)wp"y#cv!zvrALqsz!WA6 Af6bbxs{ "| !f}7H"G~:BxhW}!w7W ׄ67=XAX H׌43J8ǂ,AOw~i@ A] #X8v=#xx%؋XŸa؍ϸx85y"'m'Gҗ 4( 9\ÁrmDi9* &Z JKZ|ywyy"N}woKX d,l"bzwև巠Mb;u!""Fz6"¢%c_Ī[ģ-"yOa! td" Npګ[yr%hKj;6 Ls ¥bVtJF$ _x3ڨ#jՙ}Bizc4>y9b7d!e4#}"3b.*f###p#cz)#AJUmzE;ED{D[3b^=ZZiU]:{mjZ_\;;ݛY{7^lU/AKտu xlASFђL|X#Q,'V+XPPs•lU?%FY<ȉj259')ɭʱ<˵|˹˽~ޝ=*>7;Y =Ee/]*磆'qfFg>iP=~ނ ~׻>>>΋Գ~v~~Ҟ菞מk|J>?Cü!_Sd:bG#WLT`d0W bE_^uQr5G3].\Tbi.@y& O&Q{?rYLDd/ >8Ō7+YcݛLp2|: xGr왠1tj#gr1)jJMcIHͬ?<=tfhio~ly=t_t6YeW$[I:-`>a+Y$E@]w> [0*TtE͆"A"( $`HwA9&NيN> eRNiP.vM&%jTF)dLa&d{ t2DCU2@L I%Ֆ_h.j-a蠛-))`J(g2tBTҞ}I"iɃm6og3Ԙi+gA5K$-bYlʶ[y$5llJ+JH p,+bm oKoދoo pLpp /p? qOLq_q7 r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxx/x?yONyqË RN27g )xz_,zް8s &{{KhB?w9_wM~kN !,!"$&''''((()*+ , - - - 1 5555555530.-*)!$),0*38(ABDDDA@????@@DзJϟOŒUYblgSPPQRRSWjϦļùǺØߘvvLL22222222222222222222222222222222221212130J/O/S/W.c-l+t,,+*~*w+r-l.c/W/S/O0J0E1?18222222222222222222222222222222222222222222222222222222222222222222q H*\ȰÇ#JHŋ3jȱEM<ȓ(S\ɲ˗0cʜI͛8s Rƞ=}< ѣH*]ʴӧP :hV9lHIKH4kԳhӪ]˶۷N,Kn]Rq]ɽ È+^̸ͮF.ЮY5I.fCK6^ b7װc˞Mv̓ ^N2H{sЮK0V)Y5fmkνO;MޭVٸsMm͋֍wfYPh&L^ŧ[f釜qddty_atU rYaނ0(4XV8wP蠀fXɗX1~Ed&8*'Uu}V`)d26&n%i5on)蠄j(N}YBE6gVol!_v駠n= R**k}^5uI-:뮼꫌kȚc&FKVkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z&'@@А|(+\ L 02\a o(@٬ *(Kt"hІH$v1@@hL6p@Ǎ 2@x!0a C-]X<; ,'Y)`dADI\@B+u*d!'3I,"bGP. ZƲw&aZdbz^@,0@ cT@ &(`-sKj(&bȂQ4 H@ 1`CAϥiړb%. HU J2NWB3]X^xQ2re@ b΂`D6LuQ i`iK#GaR )AH $*̡RώVծzV "7"aAJբw["@:Xn˯bbJصb43vll1Kjn _ٻŬhUZ-^Ѳ•p eZbТn[(ebv6:nC[\تehEOcXz׹MJ>HĐ=o>ӫl&& tޤsZUEYj{PO nRڋORؚpyk"MqE a/P W bӸŎ},qO\lf{YFr_ J7UwK4r|)f sw''9rseյ/]4`%,b ;[?ЯEqh سsƖpw|h&1cc3iZҘδ1N{ӞGMRԨNW]jQհaYָfus^z׾u=b>e;~uUGw@CmS_vow:N7 t{vy;ڶ7rA">]D Yk)F8sMVTJo9V2o۹bjjNJjyRGX鹜Bo]iF9k*iRuPiu^uOpH8_Y+7x xعKiq+8ogЫ?Ȼ{H( A{tRț{{*+{M۽{F{r+黃Fwȫʸu|e&w@͋ȹܢ#[qpVs yw qvpq ctr WG ǼP2iT0z%w]u9NuoGuV<Gn<܂9. nWb|ĤoăzwWMkj0ū\ B,ƞFc`UltrpxrwvWrx|G{C܁1\HLqGzQllL {LM۷ךfk @¶n Q ggr6,Ֆj&oLkלoݬͧLl Lkیj,ԼLhϾmkkIз6 mՌjmMw #M%j-)+-/]16}8}Ӹ<>@B=D;]HJI}LPER]V]T}ZS^@`=^-d}VmhSClH r]AMv ^.n&7'>+,64)N5:3n;=9;]D~<1LBCM>HNP^SKAR^n`>fN~\j.e[.@.hpos|mNq^lw^{.熞*~u~~. -ۧ۩ګڭ~گ^ڱ.ڳ~؛ ] -Mm]XsҞ pnNߞҞ}s .DMo. o /O-._/r-  "?_0_2_) ?gm//'O;IOL'  pNAC,RmӉ>?o_b__aoE_НfO_q݁sߑ@/_sӛe8!`ٖNnio误O苞U郞屏?麯o/?_mq.O__ / n7}[@@ DPB rQD BQF9~ cH%;DRH-St3#.W5mh2j,zTĤK2lTS zujVOv]J8LeF`Tܦ*vLs]ֵK6oW{MKp`6Zن#}q䊓)3|rf9Cs聊lLhYkDk-;7ݙMvު>:圛N8= qy۽?7s+ڃ?  “$(@+ + <! ZѪKq/ʌ2*cѫR":ҵ$ R#kD Q%K,m۲ɰ2K/,(!.H$)2.팳3T P`PCMPC%HG]ҁ$-QL4:P?mT7RJ'TUT=huWctZ_6\WN/5UVu5XX]U=UYgEWhZvXjMXh!VZkŶVZZmEW\]%NIT` :K7_QKݷsUt`^~ueTݷa߭ވa%X^n6`p=۔?^xc]x4(M8.\% |jğ<ʧ^7s?=tG'tOG=uWgu_=vgvo=www>xo8gyPNAz>{9C9R\lbu0P5,;K0h:[PuPꬡA!(aJ .Ќј H\2;, a *^1bꈱac58:R|$vІ!ס$.,-\H 'MҖe.E:Guh|aSF :kp ވ.C5iUr3ui6$!.ą; O]*3evf-9PԠH{@* ǻ'v)s@(Hu. l٩&2 ;^B4R2BFӚChpGxp7x%>qWx5qyȱ HE">j nɣn={ON쏌UvVޏɣ;)J?ₐ4| ڭ7^A12t͎ySzwr=ˋWg6.+LjF҅TU_ǎ\D$Lԡ9uC|C_χ5C>tջQ )JЭHOG4~3`+`+Z ,X0~eԱ#@蛾,,+??z-x,yb,++t*k<0D D.+=z;A-몭ʎ(yJ%I&k2)&L?:`၀>8ʰbܯ/28...ۂ;90%<s# 'Bj?04L33r?83I ;D426ˎ-xD+k:z- 3/Cl4XE 9Q4V3(|Y^8]:_a8`BbDd<8c,etg7fi|`*kFՑhȱl;+rr !kLG&!v5FrNNtfqꓜJnHfND;h,LM*NI!M{L,4?09NԔI)ǹ ͬjR ([MNDNdlζ%JkdmSJI)7L&LVL4ǞM|ݖv>kim|H*%>lk mL&*hD|lml$z·Ϋ6N41v ZEGQáӡ⭤#gpN$"k$$c=7@b&O\3t&Я};rm󉎲B'ʕT(#'3>ھ>o= /p0/* c(r@ˌ@ d^ꭁBߤ-lBx"Xj? 5Ct%Bˣ 7C9i{6TIRTg!KDD$2DDIES[]^_`I19vŹɜkIvc7hjǜeg\69vdpqWvk ,8fgxwWyz\w1ww)x@m uo9'>>xlDrA_axyyy`zG/?8zcz {gVysz7{9whiiy{oz_{/|{g{{G|}_N~7lrrwjPwSx}oaGq}|۸Ǒ}ޯߧ叐nA~݈~ޘد~㰚?~GW?bοv~gaO"])?(7(/('(pDe*h „ 2l!ĈSc %`=kndi!4iA"̘X)ƎCTXdʕ-_,jTcwH&QTѬ2K$`S)qsjЫD}H&Fs,ЂbX/C5 ֮Լ1E1( T [V 4CDH ز Npd.X' P]ِƕn}xAȕ?NQNϥ.!v\q Iww[Mzݿg[{"nՇAk@7\~A's x*z  pG}FAmܧ^lEA\8V= .H#W1]wCl_E1ACڔБ%d{MěNIi%r%ej)f}IA]&DffЏ@fpBpgЂs:D'o*Y:XƤK$#1Yaiuؑ|yBpQPb, AEjxց**{ +s`,&tjj&jdl+*,9,.E%糲V[jIkUb- 4keU&: uplWw%lIk):)!qolp 2_ph/ ѩkr+Ú 7p9t']KL %)tSl47_}0ڧu1WA&A5d#ݩ`l3=$B1iV睆d _iFA/=N/t a.gAsZk9 ix3gF\KP/8yWߞЮ9@QGSUQ~b `NC_s6$y~槗YWiN~ע_ eN;־[3MA D  $xlR(\$#^ZPEDE}XRz F X4q O,hXŊm"8-2'_a51E2VČIA|!^Dh8G:ňiL,b?:(kH)-N35TM4,D.ycƨU##dD\ ˣrNb+m9(\B%0)a<&2e2|&4)iRּ&6mr&8)q<':өuhdPQ'>ŒSqgBYCVk2}&N6'F+ SfFC*ґ![(E)zQ(%LGutЗҴ6} ?sSX;2XOY*Q/(.Zԥ2 #B!P0P*UթVWVZկJ!kVjV$([*׹"@%*2Tz).BPԖ򕥆k1-2K Ȇou C\ JPǦU0S<2^.e0y̰ \f5ev/ys [ @0Aq߻bAw8Ċ-\BX h!M V&mrqes$2.( E[pB(a;6 bGӶ-,_R8I>R$6pi3]T~;_ E2Ď./-qφM&7ӭu~7-yӻpSL}+w]-o&"LROЂ6p)8RĊ >=W ?-K򾂼`pH-q.|!'iY. b5kN<.-yr[Hzם9]PkjU~tg^/X @ط>v .])ctnV8ov}Rx{ Wykx{Abxsz#^T\z>=??&A}Sי =i%ۣx cg͞75u~߮{z=w&Oղv?/gO}AgiZqQ$ Y\X`!OmmŹ9xi]RB BHuB@<0" N^y\YS D^c π^"j`Y\ >aFar Z R](V`ө] B\a1!έ"J ]Α]N~a! *^]6~Via`."A\!*n500b0"0b/"++",Ƣ,"-"l.."./"00#11c/2.#362&3F4N1B#5^#6^5f#7vc5z#80n8#89#6:c5JD1.ց́;c0c.ң=?>@b@??A"$>*d=2dCBDDV$.E#L.A"G^$.Xw.=A.TD/zA%\bTd/&#I$$dJޑ#Md.6%\E.&#H F# RMSBTPn"QPjLreS>eKDeX$ Yd.^K/.eWWe\$TeM\Pڥ.Υa%[&eXR-&0*V2e.e` fgOd0$L$cj_~A%HfEn.n .Z\.%"%.jAWjg&mnbof.Jv"rr%t~g Xg?bwr'pwrzx'4^L]&pgw{Ƨxg'~'vbQggty@oh{Az&OBy0Z(grg\brt&f{pq(xj(J$l""&HN/ցUbqb4|bhA>"/.6( ABz~i.f╚d(p):hgB#囎&霞i)}BcAn))tb.v) d=b:{fb&"dbQ(HdN@njʨ.'j2W_VD*.V2)["@LZ/>FWNkO^kL梶)0f^Eh.2kiJ%4«0Ϋ*.kzr;bګJDH) .@>*d&4A4l".A,˒l˞¬^,$+ZDh+B̦֬llyQ`'.:-Bm.z*ڬ`>$$ª/~."mؒ܂m2mSzm6m&.݂i-l.~--.-*JjJQH'B .h̢n٦.βݺnrZڭnlRmMXv,nq(nF@}Z0k* .nobܪi #./nAb#*/r/p/j/3'*A0gSA3VFFfHFte TgpkwZ+lo&.Pi d$epi𜆧.+*bz%G)218q$jZkZ+W p7 qp.01?sWp6,'q:*31crwp9pz$ !WnZ$L*L/&O,4AEpArELr>2j^)x,@sj(kUA;k.rdJ00).P-1-[1e/r631C3n2**\ s_bhssς^r2"`ZD4;.s*#c(.*3"?^*<c@.?7cDD3^4EDk7 cG{t"[[5\* Tib\m=bi#Rq F`5bVf 6aaWԦ$6e7W`%^UddW6hN N"k05gg6l_flնauL^ll] eo?dop7z ^&fA$c;7w;_s vw7yv7zz7{{7|Ƿ|7}׷}_V-W]D<.0}88a ciuv"`7g6INǀibP#iVg]iΔP~4hi8O8i8+ h+ øK .hd*8bB]mzynR'nV+y&uT h<o Ž8ы,Ϙ,Jz \"W:9}A()IېcY7.Q7AdBPaQz~ߺ::,;';+;7{?O{G;_;xo;y;v;[;;;;>IJ;C{;߻绾;;zx2]%'~[V>߽vwރcc~~K>?~~~W꓾+~0>>_닾~w>n~?+?7?? C?g?@~k???o;Ow?@0p%%… 6TDRtB3A>葈 'QTeK/S9fM4eԹ'̜=ShQ>&URI>4T>;fX0!*ylϪeѶ<-ʵmᎍ;-] -WU}f*!FtA.;YdG-_.Y3i9Z4V].mSjRlƭRF}8ƏoN|CC>z:NSW{9ޏ/.8yw/};qTo{οz P ,LPl!P ) 1P 9A QI,QLQYlaQFB8q1A 9`A#-*d4 R)-+<,9BRI\5d\(Ӑ l3!}RȬ0`C1(L*M̈́.HMG8VHȄ;椳5h 1XO1BOA U9JH(J`RT74L_TS4ZcREQG!6j \V\"!.tO!ӄXBkMW IeXvo^\( B!~]Fy4[p!h5C,jBeJ V!Hӄ%YO򏊳EX_~qAᐄ\HA 9d)+Yuhcq@ Mn31R1$"##k6[8& MdnO jr뮿f;~l [ Mڗ Z̯`mn.9;pI0NTj/(e\5_^7]a$u[7Aך^=C D~#cYэ7wQVՇ܍?]@w|P5/0p 9|\In"6ǭNx`v2=-Z1 L)`OQ-\P$g\GpSL9}AP.D!E4D%.MtE)NUE-n]F1e,HKT"S;4R I薟 p)HA쌊, ;JR61Gnd#y 2${ fS qiU4La*q9D6E!lO)?K00!ǥV cd[" 2K|Kt$&Go^,$'5EGevnUє!-* $s0h<{t|Α7D@!u9bm{CFEǗQq4}Є+L֬m-"UhسT( _ͬ:$9߱UZ|).Z;C1iBRg4{7ɝgWvp~xo2LpҐƓTYqkvky*y8*m po┅8;.{# !TC6dMcds=ρtE7ёt/Mw:YJ (Hu!j׳u]d]%.-> lcDM:T\'=m Ut|^X݌~?;7> xoyi1YsF|^yOC b+qS_!Ohrxw;d<߱ojX~V0'ZksR}1~G6GeO>(ɞm`OF/P̎N0!0%p)-105p9ppA0ADMQ0UpYO]0epiPaq0uop}}p00  0  0p P Űp  0 ˰`ڰ 0 p@ Pa}@a@. D0 *A  ?Q 1@*`@'qK31iQM q HkZ[Q ^1c1qm u|`|@ PNLA50eGq @KP pQ @ sqQ q0!o`@' }Q#M`"r"C0#;>`iR$I"K `C0&gR @ pr *p q"M0  q `,*r-W-R A"߀( kd+72R, /q-GKJR S0@$CS120#!,@q TJ[sq@] 2r3is0_7E08?0(c6hS%7u1u3' S s9w0ks/A:?0 Ơ Ns0e} 3s< Q@ ?uq5W3? @B@ 4A 4!A!t@%"@QA-B TC@;B?AECDCMtBU5esqB"2uB9S:?(TEI4D4EK4CKG4HCEtHII0JDtCJI}KTKJJ'EK4LGtMTLٴAq;?01}GRGT,MLBP $s9495PR$`3 uE7MP1uDI0=FCOPPuSV3A?>8RWw2OS6A)R{:y5UY!2%/EPXwT$X9 0_QeQ"U03& rѵǕ,R>04 WYu!?.WZ&X;2U_!VaZ Z+V[cs[R.!'bi2)i&u%?'E_Kb&?3^$1QsahvBXmOq EeGQhVjj8}D"1Si`llk6Z!u6_`?NhK51m˖i)p@jqWqqQ0Wr3 0\rPj%+ wq75C5G7.K-Ow-Ss?5[suAWvAvCpuwRwwwx =vwv7-g7y1vy7?zwJ7{iNwR|N}ӗ[OwJ HIj|7կ^܏4 b&xח}Nn|π+7M^p؃BؔZ\8wJEz/}R4O~ʑxpXxEИvjɅ~나 ط,CQ hcÎ06PO] P0xpzX9ԗz_({9y9y1F0k~Y\99E$Jح(O=ŹFn۪U8ٝYQyB79Y?UO$j8ߘĠ# F1y;ٓ%1èJxأQT zFR|Uا~9mq:uzy}:?¨g?DC?;é5/C)b@ZګyZɎ¬z+ ΘCB˚ڮZ#1z.$@:±:%.*-;fϰBEA.J-N-R{*2 ٳz1{¶Zɺny $@#+"#!;";[#,B!{۹{ۻ寸!:̛[;["ۺśɛ۾< [!Ƣ[%£{) '<[߻[O-cài=FB~,$,$L6'Xl]J,8 l~ k* ^sӐ^JFⅇ !״-鱎w|6F5Naɦ!-`ޮ{؞:|܍ Í*` N=W(RDn"0n6n>`2VnR?=&䉇H]??N"N$_ߵݮ辿]jw4,zopџ{Ź_{D"UG<<0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} 7ܹtڽ7/J3 S% 8 拫_Ì;~ 9rS3!9͜;LtٳѤ9:ըKHr״kۮ:ۼ"5ċǛ;=:O1=ܻ{>˛?>ۻ?l7O B!P~"t P _-\CZxbG! Ql >s@]`hȠɨ:!+n##H 9$LH=HyN69%6>cF/F %ʒFx喗ɓnnf՛rnign]&jeEi%*h(xhBh*JziPyVէz*jxZ{2誓)BnvZ)`뫽ƺ5acU.[>%9З*T qOLq_qoqE",2.$r*r.0Ls6,:39 t@,tF|tJLN'RV\U_[w 6_|u6cCQivo=w1ۍk|+m*e\F" R2i+c{2\2YX'8j|Fzġ嘋%hbIl{xXO^CϮwxv 䔣.;!AhES/ak;gq9jt7ny9u={C~DACHC;99:?BCEGDD@?????@@??@@@@@@BGݳQ_쬀ɹǾxnkt}ZRQQQPNKIHGEA@=820023x3r3l3g/^*Zz)Yz%co,W0<121212122222222222 H*\ȰÇ#JHŋ3jȱǏ C^&ɓ(S\ɲ˗0cʜI͛8Ej#ϟ@ JѣH*s@KJJիXj`AN դX[Ӫ]˶۷pzzCt46߿ L^}{ݱ5ָa%-̹ϠC{ncd<3ͨ':3BEͻ-)nuɒf]p.nǭ7jO> uzkIG_~ovu g ֞f% 6Sq6ddbWzg!| wa|ar14h"zHVc2\><^AX^dlEe*\v%,ˆ{cXrg%wo*ibF$mkB~u})蠄[si>V'&&: )'h *z\BzB:jK}Jz'l!J]CHgz++찈IbicZ`l6jDY MKv-,*tSf,BjZkoQ"{nv+ p +ݵ߫ Ym%6d0/]b w[:k͖_1,2$q3D2/孜2Q5,DwsH'63PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:xT>~ IBL F:t$##IJZR&7Lrd(GIBz)SMC>J. YK>2 f{_ b `*ԥ2yg⒚Ħ6fBD#T?:-A>A{@tFB t7a?4NASa-)K@S`;)=D!O~17ȄTَt|,;\ʎ>|l:\ SsU$,JQwbTg?Rԏ&5R43MGMxӞ -9K~|g<эS 6FZR]i U{,MQsi-TqգL+]jשVuL ֥i7IV}TciO|K#lZhgCGMFjD"@H֊➏mREBJJwy&s|9+:<#"D8X*<Sq^R1w m%#;UN% 9P"/}b/ wi;&=~X xW14/z?85+\H kdpn1sRu裌\cCfYC.co^de>Nُ|xosLo92 1bhǙ[ҺRF0^99ˏ.R;avpWVNЩd#J\{q i,Ҙ1\/Һu\")#9e>&t6}6K #pu)r+r4;U; qQo2PE޾d),W#>x]n7xu@>|]_Q y?9R< A&>e[ػi]hO(zkG=nW7Ϸ|͠Wg#[. £}~tuS5 dI'`WM׶}ŵv+'tjk(]U]]'v6Ub7`wHs &\~6kst{$  6 4x85x9@(Vv{gv#wq0Ut}䂂t|}m`p g~9 Wdo4wQRVnZx g~4` {cGhtjflhX~+vyps$vx}w}$|~8~nhp~9u}$6?8DG؃X芤ȃ8EN&z}8x|3>(8xhh(H,N[?CD,MLAM +|RlKS|UQlR\TI7 :-?=+27>9^$~(G;&?=~EAKOG>ISW>MU~Qn_NYC.*a.cim]qN[w^V^N㢝 M g,FZ.Sz͔͝q}P a;Rn[ qPkְM]q>n=ߪ<\ܱŽҞSL쟬>=ʰG ~>ƎȮ̾}~˝ .NNm>  ;}{13N`.^ʬl bn (,IK:og.MO0OUۛjtXNNpn l?pwyrG~?S`!UT0)g@ PJUZ%qV\uśW^}H?B,*@CSt.w. .eϑ#]C!E =ZҴ0 {8( \xSF[!zXCtխ_Ǟ]vSnk-QoE$B| CCGOT5 o<)&S7B`QT`m0*kXA;JdE_1F ,AHpGdCFl|q4v|<"8!A1?@FN<4yӥ⊴RK/4SM2ޤͦA=&,HS^=ER*R zuפ @ӄHM d}#qSg"h-Zԋ>tg[oU6HTXzd{O hUP(\u'_\)#\ Hj 6s]R7vK\po(`$ujWrJv41-9fg"c^ y&-i3t}EA(9؈Lx"پhr5cJP jw&pG<Z|bq/yg˥y駧za2{QT%G?g?znۇ_1wѥ]!XPC`#DC`5X:@68BЄC@b Ѕ/a e8CІ7auCЇ?b`1D"шADb=#6Kb;'V.b8:,ްNȎ V<2bʖ, l@ ƈc8mяp2r<&d]BAj$Lcbh;`{ MlxDZX cd.uyC,+ z +E(4PTh$%HTRd%e($|P{BbŸJNBe]ӝI=YЈ4&NLH"Bt!{NȎa;5L X\mn96.Fa"N1iK2#_ ֑~`s9І1 u(q(5gۆ7jMEd}WpyOIj$ִk9=(]^/%=9Up` W5fq.=bc};qgHE*]֗4ޟ$%?%Ifqm|Ja$2OiBnRGC;be}LO1Jq5J5^f1gno$YeBS%C $:w^2T\W)#R`ftC7exn\hR3Lfj8mg%jM8Տ'ԘQy @B Y6f+DhG ܶz~ֶ nuc>vjɠs[pZn5uiM&u|o3W=f#zֳ]pZ Ϸ!k> /t'Nw2-mi\̅)#9.:̕.Hviҥ>uWWzֵu_vgG{վv}m{>w{nw}?G{x+!?Ce풧|yw>|zї>7}I>("G5{So(yqhq@Cܣ, T,<,C=LIJ+{r\B3330 ;' 2D;F|B{HDKD+Nl;2tDH$Pwx⳼,Shxh%ê8EB+F;d>İsF.:BpF QhHz${YI8˯H@QK̿ζ PRPsKO<|(/ ́?|P PY<ϧؽދCϼ,|QQJP@K˵x#\?Є<]!?'=; T ;!E?Rԣ@>)=LSlS#M;֣3}Sv\ͻS><3SMԳ+TZME]TbTTJѻTLTkT#TN2EOKRT %RSTeQS UY[=[MabE'=C:]eu`eVɂg0 + L_%Vc9W˵3V6 88Vz=Us֑;Ws]9{nj<8{ù(õX{5q؇S ֒֐[XSWK96s^YrK7m#7n3YUٓ77nA7vKّEٗeuٜuՓ _9X]٢H ZZC9J!kXrW̑8`#} tXU( Y!sV|ڹe:87虱um5EUeuDžȕɥ\A(gܹ$-p# KP$xHz耆ϥݹ%` ى Y*.eB,٭䅙xR Ǩw'rwUH`_)`9)ЈQ)r)R(,^ު*@*aJZ*2+^EjyQ1,I * y7% q9rt2d Z&NY$\VHa voQ`/J  r])&̹\"#f"a$fboUn('.幇,-b/0+26c1&3V5b6v7nc89b:c7N;c.=b?&d>.C>FEVE>FvdBHId=KL5@U8^+xdGT~Ufe6vWVXe3ZFc\F[1;dPR&6-:ܞ=eNdgdhffidjfkdlff&av8cޑ:efX>g8t1vcwFgxeygze{`eŲIaT|F*{)h(f膦ugvgfgf}d~gz$x3BQf;i(ieHiNZf. NT{莊ivii@iiii>N6jꤎ꥖j^hj6k~3F Vk髶iknYg[i7&jVkk..kN뻎Tl®ljF˶ȦkY&ΖlmIbg>gfff߶fecebXޑ"6fvf^feeNn>nevX~b"e3oeee~efeNedd~o6d~fbddWepbded o d p  /ddpgcf1oqogcWq9&o%pjqf7gl67b)V%!F'?(7)/*+Oܠ<`1=$`؁0p(Cb(L8sY݋`s7I@e;ss7o@tsK0ݶItXr5 9'tHG]4tq}8,Os6 u=oEtCȑA#Kyv LZ+1g1`` cZ=Ye fOgvwp[7vwvfٓdWhz!ߕvH}j{]ws' 5Vwy0`Lq` ,9WqWtwja stx~rg}wyqJ獘Ty'zisxxS}΁HE'q8^zY/Eo{m @'w{L{zwxos%|z:iyȐ p$ڡ:j$! Bؑj $1*y%}1}Cؑߑޏ; /)8}毓.y~W7_+QG_: B9o~$iG,h „ Lm  'Rh"FS #Q"Eq sgC#K;ʖ/ ƬiN%S%.aA}݇\KE eW@LAP#{H?퀧Uر +p" H1jV46ha;93A M"R& ,d@88P>$B4% 2HLQj#Uxe[" VD&=9e(0v|Us` \zINcR(b%bN(y@&*MLa kZ٭N֫ ;,{,*,:,J;-Z{-j-z-;.{.骻..;/F$Q&@%/ <0{@B cpAfa ψ1k ?/C2 1{e(21qA,9?̞= =4|4G?KsAKmsєY?WW5]H22$=6f=vϞͶhmDwxY?#2߁ >L3M3ӄӄ@[~y78M3^2!/+硫:"&0խ~;; ?<<+<;a' @1WPvS *$K8j;?',jEYMAФ}G OŁniSnj7>JReTcWKIT.gU+&TU!ڰ՟T(?gYUs)Uc:8"-+""NV]iH^jԈ(9¢b Ǧ+CϪӝuUKYNuutZT, E1 9mhFL6-j[V[TpRqJ.i{uНsK2U:֧Yaki?ĽNl{Ϛ/MSwa+i:j*=*;|VzjT? j`UW9/S lA+E nP" f{1Jլ31j*PegFCTnNA==uP\xSN됙fjyZe֙fhn~3,9ӹv3l~?,AІ>4hBzю~4#FKҖ)MszӚ4CiQԆSMjT՜fc$~D!]}Ƶy-l?ztsc;f6khGFdkß U{|N"DCO5!BhBH Ѿw (GhtwP QhB73|#G`BhJV8}x'\B$ow/'+\,w9+3yKs<eKxρ8r;J?N'zϹU;68]{[ڳm?סus+A!yH<~<̱FO@;pJCB7OܧMW;;y¯vY?>@ d> $?dA:;:E"5"??B^dBEb$B^IʵH\BD^D~b8.# ~dGd$Mn$H.dM1C"$ $RKdLLeMzQdY*B#c5ƢKR$eBr%Gd;Je+"`ޠQ!'NC!"3O&Yyɦ \-%Pno!p '&f%'3g):s%rZpžBM'wfb"x.2_yo6#6&fo!Y[5&\_$%#rـ =9h58sczŸ5YhAb*j߅nvhڢh+j砝r(ƨm)'~#~(QBa9z*iz)~g|ZR(^)(^)^3V*4iR [%0V'£H(oY9*z Hhң6@i~&)'BHH*ڣۦܦ`BnV^jܪ'" Zݬ=0[Zr*Q57*Y8'Eڡ=FZNڻfk*ZdڼkkZ+ lZ®^6A=ľ^BEk:JlB6l.&lrbl2 kkkϾϮk2-&2J3@5<*<-*-nmF^mXFf-S׆ؒm؂վmݚ-Ӫm0u?4mٚmҭn->N.VN.j..AحnڭB .ꮮ\...<5.,XήnG.-v/!qnn".n6/Vln*..HAo .m:/窯//֯///BɈl?p,N"OABC.  44dA0 Lo@H AHR$P |0=É0WM R-M@h $kCTP9 ӐO)_i_8mAˢH uv|TQ-Q8+u Qsw-KWoKpqjPqDQQE!@Y!@@؁58qrG+E}X@ W54?-)wyҘR +tϾS-22G04ʛ@-ʓO2W5W_6o37w7383 Ze93:s93;;;dz<:ϳ=3>3?s=@@t;A'j5Y)CO1B?DKDWtDE'4FgtAo4G G?H3I;[GD2w5XXR'StYZt[Xu\\u]]u^#JLŨ[^ouaga_ubSbGuc`udgq_ XqnLs2v6 xhMi@vj6Ekyv iil jڜhg6 lKg 7jvmmCn+nvq3oǶr6qKw$r v7q?wv6fvpGpws7t7o;z#wkt7|qy7swG7p7_zmxwwv#~v7k緁'wxwt7}S8O}8F,8`O6eO8aP8=xQ8D۸?vMWvD0r`8Q yPON#9N+;yUCEWD[C4EK9Vs9 1L9JyIHG9GC[9{yCWӹ 9Yy?۹woEzE#D+D3LzKy9;zC:+k4wAzRSěWz8:ǺϺgDwT+Q!( xʧX48|SB` ;B4L;5::0 ;K3Aؾqсž+1@8ϯG~>쾊1D | ?PC~Xh;BHOwdžt @ @`߿;&TaC!F8bE1fT'JS"2%*yڴSJ/aƔ9fM7qrLF$$ie“)9uONMjU;~|A! D4Rۻ WѦUm[oƕ;n]wջo_l'0?봩MBp{xS32iC be4'|+O PoA1C@JrȐ)owv(h z'!,Ӻ18"_"Q0w`}MZTĕ5iM| [lD&3g3V o9#/o63"v'# K ;A,әȈ+iol25;-z 1*n2EAoM|DŽ$rěɘ?bd+U! <B4VCTs+ @HPfZA쐋CI钑TL~@ҏtYHcfi2ZC,H9v,1L܍~DVxj),h&}'9$#K;p,\(cԩpe$^BƂ|&c(u:Q%5IQR-uKaSΔ5MqgԨFaiO rnIq׻,ha)^**wʮh1@ӕ"IP-(vbmXXPUֳ3T <$X-FV|Mˬh][:DZʪU`K* _Ww`# f6wb=Zܵ uڄV"P d-AE1+]BPedoֲNI*C:e<}hp:\=w!lt;%viͮruTxv]O]E|}^YV5P`6`/ v OG1a oA %6Kt w^<^81ρ"Jh„8Qk G0"ؖm}C'[= ;6$|yz~'k}Y'oB>#5kT_{-=Ox9(;އcQ7%UOiqOmg@Jv1OoqhbBy;^=w/ xӢ9Go㆗ϐzG]={ӝ>%qC'{N,l;Y|1O%#!Xۿ!?/mo0PO%,BЄ~O,0!]##pcamkPhu/opsYgps@pO}  pP 0잠&֖On$H 0 p$:M u m/ J. /pp BO.g@ ‚ 0 q"|.rNfm(l 9(L?XPjo&LI nބq.|\qŠqNoZ ĨÞ1լ< -[rq&Ú1DdqQM 1l&A&lٮK ސ1`.N3L FNOj- Vnq JDQ" $L ,^LAX.h svRC c2łR(2r4A*a(t4¤,^-8b ){ m*׮R*&!q$+R,lf̬"(AoQ/k/Q/M0e0Ӭ(S"s&*.12/7.;s>S4GJ4O32VZs^b1SS5oSr4w7{%{s6M84!89+9]_F%KJY^ . ;9꿘;S3< j/&֊As^ 븎 ó Sk>@+vðJ@tAS?74A F-49Er4(4BI&F@?ԄX7 BRx˵vJaJsKJTDKmkVK/Qb4eL4MA0>[]JN4]FK>ՓG E@-N*Z ] GeG4AP RRA>JJzzwl÷uǗ|v}wvח}wjuxf}~VXs5"x7! b:$!ДI@'!589xP>؂=t C؄gCQ/A]8qxWXCMw 8Q<8uoy}xeaXw8;8{x8xX8_xX،X؋8JXX 98Ôُ؎ x8K#9%9Y'"yW? |ǗוWזWe7~W @VـטWיqkWA"j$\9s5_YכSMWGy]ޙpY|~ٜ/)W# 鹞qڞչWס!x繢C5Zp;o?Zx))MQZC:Oa0B*i:iWbIdkVe^&ff&Dl9jYLi!fhp!nfvEkzHkFjql&16Lt Jge\ fB'!gl'!~溮%?`{sGT^'vZ0+FI"gIKz@ ²'{|#_ƒD$iCu<{B(d(``{"h۶[|EZI^((_uDBŒDaȆû(:gXpT>H;S'\`:"ܞ '] /(2tނxh6 a(%>Rk]M!Y>^~7I7MXWaG~Hg%K5Dt⇕Td>b޾N>AT^1\~4>}?xe+;1ԽEIMQ?C X]eimq?uc}?w?_m??"r}o_Y__B <0B1:|1ĉR1F7zȑ$KS U lGLQ(cIB}fo@=s&M (OxӦ훛\9if́s$*Nh֮\-VR*JRZhŨ[1qnkK2+,{lNmWjqb͜;3 kФC HfD|jA6T6CG΁vGb,Qk1{'/ὅo`qhQ-h] v޿cGJsy$^ taGQG#G~sLg1 uT%A} ]xb b^#rDW%H"%rz$V&5EWѼ >@P#B-dy=JIbf@ HT(%AQ 4ecis9К3b)&t"էyyiЛ)V,FZڤ}hCV4}09&I.y*@xjBD+\ymhP2Z+aiGwr޲G`ma{$Zha@6 ոՊW{)f龜BZdAj &Y*QI)u:<+VA! IArH',%1\~[L+Ȭ26]F#M*+ߺREKPKC}N4Ol'@3{upzLtb}fM7R?uV1pw-wIdcнku#ޔ/xa 9ӑ3=9c9s藷~`n:ꤳ9]3sD>z쯧{ -p-3uT#4NJ}.AIEK5j"*P4O@LmPӏ~?ꗿF %.!iBHR!%:@K4`8* $ SBh1Ssf&4Q"IZhaC 6%2щɁb Z{~IЅ -Ѕ^(!֏ Ab;z"r蠋Op Aս%JZe%h_ LVdHXJA)H&;N X2JBTTd[4)2軖" YҒ2$.ŠKXt~Yڇ0&*s9.yfFM a  X'* DiψQ|(X>w~+ h? |" cC 3ՈB+JbEgGQ}B#iI2QT+ehKэ3hM#zӇԞK]:LCjQ!uԌ&UKO{ ըJuTUլju\W ְud-Yϊִulm[ ׸ut]׼u|_˺ ;I*umc Jvejhdab{Њv-iOԪfXϮvmovXM4*Yv-q*wmr ]BC#jw%2t^]( x,pw}B.lA x.EYX$ Kx/ kx? x$.Ox,n_ ˸[ox<[ y6;M 8 _#@-,F7&97$c"; mGַ/Tzn5sYX:֭Bi#Ϙ.H6rگ+LD=钄nyՋ^]h}^lO;&hmhv-mo?ֈ1d-5r,7ݫޔZ$v76Ҕ5{]oNDWvPk13!1tE&a"a+ <6.&hg")<vLعӟu;d=mw=| ~{j+~g_KW=O_~z^ϋɣ/}sCozΣ>_='?>|O a08k/zo|'_d<=d<@ABCBC@?@???@@@@@@@@@?@@ACDI^ˁȜͭӻdURRQQOMLJHFEEDH*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͛Eɳϟ@ JѣHܹhPJJիXfmgZÊKٳhǖa׊\{~*ڻx˷_>Lݶہv[&xῐ#KL2_ >f6V9ڡN˨S^ͺ΀7ۧ:i3 ]OKg ^mL wͼ-Nsڞ^{ݺux9bۛ 5퉧GO55kEny&nu BµxFye~v BUah"{AhFx^q`%ט"W]]v`Di䑪P`sYFa^JxⓃuWWv.h_ "$X&tv&$cXnHfyZv_m]袌6j=Y(vf+f!m5Ze M)⋀uu説jLp6)bAo/fR e%Xz&z뱤ZdZj-wbpK䖋f 7祡f;فY& ;rӾ+Za)yKj g!ECdu {0C'2E#G2͈p @&&G3$,_ PGVsI>DO\w}^Udm6R`l=vp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"F:򑐌d$@JZ|&7Nz (9QL)KVd%,gIYʲe,u^z &/)bҒLf,Jd*t&4IiR󚚴&6)mr4)g<':u f|/)a'.c+*e:ĝ&Aʅ24UC#P seF/}>$ QbHgJ{ѕZ.$Lc:St7MiN)L頂Zv3FQZԩjDSӪlgޡIJ kBZP ԬDk?ժOӭj:.t|@Xg^a Wy]g`)^MlbpؼfRlc:Xp2+ PJqX : Ўֳ%IӖVdgE+[ӮV-nܞַxn_ޖ%q[ζMns*]F׺-vk\"w.s[]5x^JMxL}_ws_WHoẏ+ ƫd7Xv)l cQ9b^vv0S|Ϙ'61ML`!q\HAZII(@ p=aA9h*Niqt7opN@*b@`!   RנA{~>&]{ [?B+6Ox#gA/X'V (@ u 7`>*efr#rTd:^ȁ Byh~ x<_4@ =|AK7d0{{a"~~x~7|| ~A}XGnwWW g AuP%7 `wGI8=pFGM!WIUH4xDPx2uׁ9aHc|/ `-SR-W'$WvZHIhX~dbaIWIO OԄ`TzԄ'pItT8 ~|p!(H4u=`eZZч% *`I*0 8xds{6~WW8t|;P2Gs;Wi0{HGh aH(wᘁ'v/7 Yف g$~x珆 7i r|=q@(X=tzz5yuE tp($)#&/'E| qAGYQc@t@SQIngX nGX}_   Gsu|sN'5ItF>7' HxQgXS7pi阆unٖ/1u|#'vrpwa g|t9YFV |@ ,p]U8iEYJùE@uEfͩE,PqiEIYWtm|@9YyT䚯I}3P P'uys!7jv9`W&\HJRSxI L ʂZڠq7xVǖ7Wr'ws(|yBLǚ3ڗzM1::@ -0{Wx"x7uH_ȞXj|7_f jTVjaʥ@w7xF{7פwpKxz2xz7{:;©Z*ImڏgK '(٣iʡheʋ:8j`ʨ@ѐQWE8(x{ʦʪS$!(цoxiy󷩈t?0.B.9N5.;GF:&^>+AN^P^>J.Wn=>\bL؇E}Զe4n8Nٜͳ@Ԩ@ru~~C.|^$Y鑞]N~rUdN=x~>\NvMmƍ쪭d\BlNM-Ct\c|m>L ߃؊mlj}>o`}њfÎ~ ~=Ǭ,Ɯ =N]ڶ]- o؞w~1R c]/|B_(l"{,|^/ ,mp^ _c r.oK-0?s,ϼP LP]:z %_ƲnX^O|rBwoyOa}m {ȇ?ʚlǍӏDž?o/ؙ׋?ʝƓOͣܧʩOƥ̯u̵_ෟo`\?:v04|P^/U1ԿAʰ_P A Ֆ>l $owefL@> N)V\ >QD-^ĘQF=~RH%MDRJ-]S̕m$HSN=}TPEETiFM.UTU^ŚU֭NrVXe͞EWjݾW\ud{S^}X`… Fx0`ō?qdʕ-+|Yf͙99h҅=F=tj֛W9Mįcnlnúyp ‰6~|wr巅ҩ9lUX^[zjK7:q?w:>pF5jԑ*KYF,}Y/ 䠇9+N $y'$\D2K ETEZO#:1DF0+DQ 2%R0{77S^ %0D9:2}C,UQ BJ 0BıXk!aXcMXck؆ h jřhncn%p[p7o}vb7zՅ}ㅗyw v^ >Wm߇x~f8 b5^1X^Eb^vۖ}ۗ}^ L YxB!.GDbD&6щOb8E*VъW BQs0_c8F2#T*aF6эocBwюwcǖPp6HO XHC.|d$F ދ#IM'A94IҔsRA$Sҕe,Jc e.uK^E|H%z9LbӘDf2Lf6әτf49MjVӚf6Mnvӛg89*R\":$<_ȹ |pG%n#M"#*""b h>LVpj! zЄ.@kp(D!ѥTpHF7PB -C#:Q QiA`Xhԣ HI~39-~ S>AE1*եmK"U ^uHx ֤xV+$Cs*P5@t;cQEpV@8RkA':$H!RLPRv8l X4!CdT f&0-ib#!(D@N%:-lf]HCA@>EnCه6E.T4djx#^t mpbB>}-d7 LJ:x|\~چIJe[ !6G[N%GlD²T. 70{+%.b !u 79XIkx!`Vfz* 6oN#,g2Vv^ʞl73&4. ѩ,#ݐ#e/7_*Ḥ- '7dSre_"!=fh+ e=E]{u_Ԥ';la 蔲ϝKʯA/fut0'd'/ؖi[1K5W;;%՝V:v5mvUÍ7e FG3 |1]-~ N4L@;| :dPσ!G_Jeh:YCzC47}He.lu;w&/EMC9kW%r!5ޟ6 nˇ7*]o x/k'xlQ0=DOy#ǼLu +>z})0mE6 .]GQA^,>_1̕ _$st@ n<>z`*i*=0+7 ,L# d0 ī ?> r 5 @4k@sG+l= 5@ALiC82&\ EWښCB4: yx 1 2 3 4 5 -789:;<=>?@A$B$"CTEdD%w(aFJıxDUNOTGLH! S T UV%H REAřE]ŚE]F`tb YN4edf$ YLItijD4.mnopq$r4sDtTudvtwǐxyz;rúGǹ6;Ǹ`ClǃdžDTc"JrH'ȉ$ |5.#sVstCHB4ɇɀ aK:JJ(#JʷJt<ɛ\Kb²DK˹:ಬ:.*# ȱs5D̠`JtMd+JH>d˶I̲LKKL̢LҴKΔZ:3B>h'd" ِeMWEٔ QYUOEQD5i %%EY7YPNٜSJ=ڥeZ Ylڨڰ؋ ڬڭڮگ۰۱%۲EY!@۴U۴僵u۷۸۹ۺ[mۻ۽۾ۼ[%E5e-\Džȕŕʅ\ʵE\-\[UThSӍ[M[5]u]Uו]]!`}ڽ]E]-]]^E5^JX[JXu`E$07AOhmՄMM^@$x7Ї{]|}ۼ^^޴mXZ5_M3ɇ%pߵۃT-M_`&f[!hYȿF^__ V`!Mv`!>`NQ` aF[~x*za  &⵵a^+]^__ }X]^IbS^!pWc)0Mc!ЄO@OaUV5H!7,c:en4N[ww^p^[Tpc3X崵5@fev7b=^fMw6enhN[<&fhuۊVېnn_iFhNft^&贵i"cn靆fF[f-j>jvfu>ݶ5-hhs.`|F[}gvޜiMf=(VhDNj&N[^Yfjk6}}ej.l+TH$VkY^6h~!lʾlkluЖEk~Ζ_j}H6ݭi6lܶkM~[ƈ}*0E>muTg$>%%0oa!XoH.>oV Vnff3V_foo^poOv^7eolwۺ~7p+]!hdoiOp/qoMfq&np7$wpFM !kMF%qWn)rq,-gp5eo:o;Gr;scI+s&o?ws.v w1O6sǎn>tBJk!h7nWkckfOS`}~mWWXhuUiPuuS__aNkc'uHWSPvk=d6lf[mhuNnT[OusnnuMg:hM`Զ$@7i+ePw=wxf%~iOۇG[xO޶^f~^Tx?_葿x!xNwwgۙy! y7ynxwx"ymz?f?ntW^Mx^puPJ@{\L_XcT(W`vW/9VbÆ{^{{}7B{FZ@mF|7Q|{|t>.P|^Ǐ|}'}Y{My|!|zZʯ}]3vҧ%>~z}WEha$Нݭ]?][oM}u>,G„ \!Ĉ'RhaË7r1ǐ"GZI$J&SlY%̘Wʬ钦͜'qY Py,zѤ32)EPJUXfРCrJٳ1Ӫ*۷)Y)ݻ/.l0Ċ3n1Ȓ'Sl2̚7s3ТG.m4ԪWn5l$ LBl c7‡/n%LG7ҧSn:U+Ǔ/oPg:¨H:Ϙn TA۞{.;_|+<Ǐ?>W~/k~}g}z,m rjat?¯~s3p jA35sN> &Ck oP|<`w@DdGh۳F(݌Ű81-FsGk;lsRuw8KN>401 fCsH Td .Q~¤P'SQ hH-cZ(4lCV3W*0"ƐLedfY+igWjdy+S-&i,q2i)u~ӛ:ΤsI+}Ίr19OŠ2(*x(@$щR(F3э9T28*ґJ:(A tä.})LcJT6)Ns:)P*Ȩ-*RԥD S*թ(OխrU*H*ֱf=+ZӪֵn}+\*׹ҵv+^׽~ ̪,bHĹ kA b}j">w=v^k d? E#(! LlOjTAk}6MbCТjJh5sЀq$t8AhVV'PV& ie1AaIu+ZAApZ>< Q"T;lw;W5Е.ubB0{ٻ7 -;w >l kË>/DvGL$C #~eZVFkD<˘q(p Ld8Yxs1R J@̇@3cA\ ;@ LЫr`<@y8@4},FHӊj 7H0Cz1oO;3P ]!XzÄn!EH`hmfDrͦ% OwL>*BSsWr;9e!@dbo`-un70a_-wy"EFrޚrw 0jë&k'Z\E8N7 r!7!} d4wk9cO8_qlIb=0 Z|:T8)lժ޾AYa Kz\x޽,b)gNEcyu-l΋o x;E;#oK<3sUsc*s̆f`7rZ% m")cX̟%,=GC·Xia@8[8Q;$@8\AcAdE=N<:p1#o$*?4Sz;DA)" I˱=rPj X#yY@ejTe 4J=^6@B-q= VR/|Z[ ab.fNdʝ4e@T+]iJff[kƦl&m֦m&nn&oo&䝃:-r;^dZ~9rVxWstPDZw|XvN|yfi'ygpx{֧3Z\wnt}p'UIqʒ'&.(6>(FN(^hŅbhvņrhD^|QhN(Fʼn`XhEŋhBԨ2EB#BDR)GȆB$;:H6D,*LD*@=B,A>]Hi6+͗ A&,DΩC*i]Ԃ–"D6*D )B$)*|B#DDhB-`i<ğ&=&J!%HBi6)t8+*D$+.AB A?A*F鳊hFB  ij^jB+B+EC4œJ+\lk  C빾*Bh' ',j+BCCfEª hƞFj>BA Zjzƒˊh6& ,*P,&Ȗl,"&D ͚K6B: mk@j[Z:-&DԒ:,;;èbI ^&h.BA жlGmf+ ^-n㖬+i"..:"> E$Az.V.W-T,%PB,7\7,.7@/ &/6B/>/D/jz/^*/o~v/m *t)AµA.B|APâ//p'06p0spޮgnZpc/8o 0 GH/ Op G#pBX&DNnJrp'10-bpB$m "+1 7@ЯB8*`q*D {11B A ޮi r RD!'?+,Br$K2ZBX1/):";2rתEb&7Ct2ګJr,;*-."B*R S2D4+t"B4r s0C3DnάC,+jB$r.3#{F1b6oC(sJmZ!/* BfC3"þ~må:/K3F(tDPi=;DP/^t83CPz^iԂ6B!VjPjI4B$-E4=@j?S#cBBD' )tBX4=Ѓ@CZŊF3G?iU+H?U'VgFCR2R${u@n5ZY3[3u[wuUO0u]_\^5`5`E`]`buc'6CSdO6eW~i=e݁Z}N9WڤfKi%U>#h3(jj+lvv |@%f률*"n?(%P;sKγK>wqC(tZ'ն~tg|@ɶZ)vw+lR ^$y6w|7}׷}7~~71ݓCD9CJxD8C?8C> =98ʇ?LW/_L'CMFQSwʋS̈;83yN{"~#W(ECqg"0>c.n_26Yѣ.ZEYqѓcaC!F8qJJh!?vtF<2 FГIoF7qԹgO?:hQG&UiSI0Q Y e0aJ+[Jx eNѦU;cɪxIY-ibN͵J+HڰC:bXP_˗1gּsgϟAH#OF=prhׯ4) )%EMaVPNMu`*F-FȔ[־{wW::g49z7s>ХD_z1>4b̙B#WcEZBҐibiI,QLQV⢅98=dCȖ! y?/@ǜH´%R)+R-/ S1,3LS5l7S9;S=? TALBMTEmԡ)( >XpK1TS0!͈6 UQI-;H?UYmWRUUa[q%V>@_ OZөTcMV1 5Ȝ"VikVmo Wq-sMWum7ӏ܍Wy@9,6-W~'!3RW!rka%6SI#~)8`7&m]#vXAF9WUnfgn%Hee}&hVNYy钋NfW&zbv㧏{TJq;1dgHǚo! ۭ;{-C{~u'`_@4@. t$5|gJI A. ~y!4 ]D{_ Ѷ$0r9! exC0#dtC9$ٌxc9~R'b$XF1e4јF5mG9ƑsG=}#GAd HE.d#!HGFl%1I=NR$'=JHRldNHJSRd+J8"- -Җ|.y_ ,1KdR̤NA 9RbxĢ zB,xQ Mq昅|Ùw6M=FDy82G9w"݄cVa{$9 G~`aU4 79;3[(=iQlVA8ԡ#>Ҋ*}OqSV2V@E\TaH *`S}J(2uRh‘§O,u xi]ar|CD8frPJVx|+*ѻ5pF rqLZǿn!&,am}ljV k# TDU+h# Xղ-m궔D$ )<*ٺzvHhm[^1rGBv-< omm\EbKo5[_׎/xkZ"r%h`8׿,o{aQ UÆ|z\;RشuN$na]vR1+.8)?sMS}a|ڱ#1O&1H.=LdWŲӢ V`3j<*Esxorϝp[eAZ! pPhBK*S Is l MmRZ KV4Dri8vGeWZvOmaP&bLr{ٳ6o*aF+4]f" 6Q w'-tsmzlb/x|օ u\Xk|?v'{6x;6 =hK[jU| Ngq\- !G_m TYh ?}Ȼ+,SN}!S=`Yr}u b΋O=WW^v@roZPz?J![A":0a=KnXK4=-?:ݢE,h Zn.UGZ{?1o׵s7+vsO;|?ݟG.t]O믞/NAЫb b@&lp顤N&az/i4V,JBp˺LhRp[;0APqP01xe/$ P  4b!@KdbN 0Pd)Ўڰ 0PЏ0 1 1QQѝ"1̐'Q /316P/-7UqnM][q1uq$ GA82"p1$gd114"KeqI'Eq<`E%M@t6Qt'GtQG2q2W!3!)"-yk,r#9#=#A2$Er$I$M$QNؑhޱ%]%a2&Y2&i&m%g&ur'w2'y'%}2(((2) ))*r**'*sb%+i2+r,%,r,2-,ٲ-r.)'.2/(u?@$^.021(11!+'s- RL V)A" j%`N2%2u5]'sBDҁ $6'66yS&S+}38q88R.  2a3 ^ARA45B NS $369<#+)6A3=T6qKA1"AB{RB_Bo@CS./)A:.5"/,>;]d.1 TCS'Agx 2@FgGŁ!BHu#FH IGHm4Gyo|T sGGJH"ItIA{HtJɴJLJTKKtHO&L1J L5MJմM4NKIPNqtO4OtOPUN%NR'5PURStM?uP1 :*?Ҩ C#Lt,@SQCVKYEbS!W#OmT5WEuNP7Q?RUSgROqJBZ BkF[\S\Փ\S.!>)GUab;t@[^Q'Q> 3_^a\c``_R`aS9SU!9]#УD5V`Rc'DA @>A9v8O68S7W7[;rJ.U'##BYdhӑ}eWThiViˑiMif]?4CV>_ójEkkIigvT3vlV/6ivlk21V1lljviopnn6*vp6%qq!7"@A!#!^Z&*FJH"5E׍(4&r ZA$Ra!S !rWSwuYw] B": !X"5xtΡ<(xrxzL#yby ws17x7%uCb"1"F.NUQcKh4"0η!x'{k:nCwnS_ݡWRAvv?7[gu1sbsa$~Fg4Q8UxY]a8e8rmxSDQ#X:):^DdF.D> H4 B qd( ӀelZa=7~b G AFaCEWaD5VW T^4 Ghvwwb 'j`k ƸOMDU d*40q;#" 8|  :>a :Uh!W1 x w " z@ z ` ȠϙYf*hY'YyiyBO9Y# B$^76t:{q#; =aИWJ5uĠsXsC B!PD̗}-W14?@aU#^BY#8Z$>#8J⪍G>*B+*Z;,t-J".%~/:4wz}Yx$$$1>1YC(Ҡٜ  Z ±![Se0@DfA9>b^D9 {?:(z`{K9 JYs{cb/~E>z>#:>=?[@pF#xIVE"dB*!bdX bX rEܙf.G D"DHhdD!Q(üŸ'x`??_CB`LE_SH^VI_THaLEcFEfBABAA@B?>@ABBAAAA@@@@@@@BLX}קپƼŤƝřėÓ% H*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͛Eɳϟ@ JѣHܹSӧPJJիX6mׯ`ÊK,Ohrլ۷pʝKOjӲ-6j_ L0ٵnM`bI1dØ3k̹3K6whғ&FLYBОc˞Mf:t N_uAWlJoWʏNXʶkν{Iӫ|'i<:EGE6ソ(t C} gjƛ{Vx giI(لN7aod h(8q#Jv CB*\hbG"px!}n| 'L6VwP7ޒKޑ*~(&s ܅zGBiiae xM}-r4:VtB&"rb0 zG慇駠Zr-Y~)A%2&hZ%*ꮼ+L>Gre:euVkMZdU쑤J9זkzDwZ|j%koC.UbHllDp 7 Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ Hİ8("H:Ћf`@T ? *p/U AEX]h8$RFm"!wD'0At@F=zcA l8AF1ьlHϘ4‘8B48&37؀T{C-&CB#1ʁt`bA-lA (@ސɁ"9"]q"]СqA'F OsHL nUhSUh(Ẃl`,wӠzB$HbBO }n2 BP !ayͪX*՘E-V $ʴhC+ P7v \`@O̪ c\ [jDY]e|(OXմTMcGpDzEq7YT s\׹A)>uw=pM VbkӚoOjrE.6ݫ@6@Sq СB@n,; >=QTIu=qvuLlB5հR fQ N-8dA @zGq/. cIS upyTIJS%bAʼ3g5ŎN3) 44!e|AFHCmIA %/BEGB J@Yt4 ]p8fIѐ# IB걜vviRHDִ.ȭU\w;]h$3j;._ CG]fh:H#~?„nxC05- X O`- 3'W0g|?9u9]l^21@97Hno-)ӝkL(u S#I̦F%7`5Vi t%z4OO/5!'=9j.F(%0Yyc"ęDA%G:#%9щu|ߣ'Z=„Z~Ј6IGldMA'iz~>?!eqd1U'Zkv|ԤXr $ BeYƥ`VFkBf`&`5u`ƇzuHᇃ(h჌ {}(n%\@\^5~mH_kq]^ eZH8`h0$B L@WdHPFM6Vzy0$]fgu}g}aW4}pBh EHf{Uuc@cd@w(X0c@pe( VhfiifkFCYG[ިv7x5eeht i$jh1gTOw& yV9g !S HfD kn[L7QonS~WYZИucGuTwx7z vmMmmVbUɌw}G}l ֗lRvjhDHq0i<'VHIsyٙ1' s jFY b=.>K؃)YyșʹϳYyؙiٝ9y虞iٞٞ9Iy:) ڠɠ Z;@L  :$ڟQM@  'J&:Y6z8<*iÐ0 zHP щ0  'U˰PN)T dZZ iHð pFр `y) e:c0i pkjڨJ:Y p;sF СYn  hʣY HɪڪZZթ ; ; pR J1z誝꺮ٮZөP0~b Y iiP kzHҊ K;;0;Y[kNP*K Mو( &Ji=):;;Ii i˥zΩLk:>+i]ZϩM ? z; a;t  `xQp@{I1|+[+ { 븘ky˸˸[˹;{[K{;k[K[+k{뻡뼝+?N )D&f+Թ;Kۻ犫 @ϐ@{[;軾ʛ|~۷_kKL ܸ kR H0 +[+²曺ͫ˿ѶJ qKʳ8K6k49; HPN?`O E H,GMܵY*ZSP ikJ JzX< moq߉ jΩ@0I̵NiHWԉ *bY #!-ƎLƭȱ;  pˠ `*̡l\z||lȳ̝yw LH9̯i }r::͗Κ  s||}< ݨ -]5+MЄl2Љmm %\Ҫ(-M*͜4]hi% 6=b0CqD>?P u ߓ*gD N7f}hjl4^uor=t]v}z|p׀؂-]؆x؊^،،ؐ=م-ٔ}~m٘rmsٜڟ-ڤנ]ڨq}ڬMڮڡ۲٧D}ٴۊ  D5aۯȝ܋}ܕ-ݨ$vWEh1I a^Jw p}wmbPv=mm=$KKh{٥YLhLhQ|>Ͻ>q)!#%.')+>"ZGZ ]am)nLphQK yF^IHMnOaRQvzt{.rn.. ^莞鋮蚮nn頎>Ꞟ>.꓎ꊾ~.N^>먎^ľptn~ʞ>?8$thn켾q &[.b߭X~ڞ~迮⎈Ԟ~ >[in=MGnL{ %be; mq\MK .O9_;?=?7he\hU_nZ iikPC[gpAacegik_ Քukk]} -Mo%׸=mܞ]ו{6_o??~Oܹ/_1#%G)30sKΟVWqݞ߃Xo0ԄQD-^ĘQF=~RH%MDRJ-]SL5męSN=}TPEETRM>UTU^ŚUV]~VXe͞EVZm݊ŧڸs֥+7]x2,$yWK[!а=fĠ_~Mf kTzcŪWTj_)[ƬBϠE6 g!|}u͝?.8i},\Bͤ'\@<9:pgi7ti>Uf>8y.#o :h^სqγ Tg10OdT(HFibDbZTWq S(a uM\.? 4(VH%ҟ| Ssb5UeoV2- הpK޻nc(jc^y;M_:ejybr٠퀞9(r5&lg Of@E l4X;`!@5:=BIb) q ZTalzS dٴylޮ_/'/DL]9q!tLuT }tip_ ';a7h[UuUyH'w*au_g''oLʝ[͙?E Hos=.6^B 4a % |-c$ ޗA<&4w]?A$bX@[=O !6/ "d?`D QqޤX"M"9UA$a AQuAVr@64T 'Ԋq#39JBucs3@яm|#YCĎ#O(1h(HLi,W&!6+Unr|vFE*zQ"3b!D/rKC1 b&Qʼn97!(;LA|d|웏ɔ$ 4JcJӜYM: wyֳ)':iN  cڛ~:(xiP 1R'C 򼡼IJOP~Y ^'p 39^<%-M "҈6f5SE/wUo TUUլgEkZպV~jk\:Wծ]yk8Wկ,KXְE_X6ֱlX[&t#lf5YL be%aΖִEmjEZBmle;[Ԃv?t۰*] T;6mr{2l]Q~Qݩd׻쓮n wrջޅϼtKdV&$JTdiayN5qaNAe!i,4זftkNǼq-SRt"}lVI;({m_HTM__H1@![O~5\ImMxRSNi|Yfۦm虬 xlei9Z悰Y-P*:nX{i=O}z듖o1w{R<29́_p rۜ>xn4Eᄗ;1 y]"=AtI?՟ntu6eX;x]xnn-h;el}N oV{i/ˁF hHwa)k6nH˽2x%zk\ޥ'iՇ| ~7aMn=OZll B&6/[\77L.lU2՞ wSG??s 1)*bA)&ēC7    4DTdt|0XC  Ž0=bW$T% =r-d(A B-Bl/0è3$B'B(C#6BC{9:4C(B>CA$D&BdFtGHIJKLMD؁OPEIR4SDTTUdVR<xGRR|ӽ\aP\m=ERP\_C_7^5E}mRsuPLUPBU |}`B-W]Md F}^P \T ΖԃME@N#%MJRּNNa2I~NTOEC]Eb,O@b%fb¼[}Vi5b#+ށ%6҈b ~a [S,=$>,T'/a*vm`>N2.d? >ea6R*y\䅽CfxM!^aE$P(XUPQC(`eX.SChXSԃ\~]eWfMfԃ4ε[f]VWefc$a}өKoep-c>} +mf&gVLR{f@VEDYeDeUh_ށW.T FUǁEshhZhh[hicLŌ.iUnŕfUtʼnhFiŘi\韦Nihki.jni6i^jjVFVijGj6IkivJ>kX^kjr빦Ļkª뽦뿶+,\+\DV 5$=Ʀ kHl^ ̮kv.vz?@mޚޭvm m m&m ;/{ ~9鮴[: ΰڞۆ/vN knr85u&މ6V@v>o .\o'o6W-[wx_p o p p 2_ O8GqOpgqwq p wqfCl'"W#Gr_W&'r g`9ʎ6{Qhr)rq1`n23p3ئ:q<;s#ߏ 0Aiݲx5t+r>tq8K1OONuuRsTS/'6b2gtBrV4&8qU?pbrc/vdo^uft:;T'BQC/* Xrgs_`77n^}8zhxzwww|8Owkyzw/|?xg~`w'7xI@xWxoxxx7}w:gxGy_xyw/yy7zoyCW;gWuk!;+zo'.t\aoqeoqtsuÿpǷsȏ)ZE [FuG.@l+ab|=_}2? 8 u pp: J8!rD mj!z!!8"%x")"-"18#5x#9#=#A 9f@&tu6ry/Z*oT*49&bDyfx]&N[vi^f.rue:tY]DSA=Uj!HAVqִB ) - 9Y#BФB2.Z d[l1)k)MBzWz4*ly$|!))F*@B[m٪ꨥES +p"~5G8M/J([;/}JȸyݛMÒ |.J"."޴Q -׊,j %JM y[b|$!\S/BNL3!'C! ̀C3cL+f~Nۄ-مf_M=S-roxs7m]y&6Ψ/'=,)8 M@'36 NMo83cW,b')e%SێnS]gn> xN(d)_̓&D1L)OM-^-!Tqג$g+id}+Izcx|iD‚୼kj2>|}%~p7 J*Sp5#[ i"AS~D|& XHH!& Az``(fP+voP, YF5ڰM^]p2fئGq5Rԋ(5Rmb#' }|dIR$&3Mr$(C)Q<%*GI̅4 "Њ,b YHE*)LB(uqKIȼ- fde,fQ[B&8yb3˔D0f"ĉ,oӚ'>Jo&{TfMi27'Mf xs|(DiINPBEh\ 3ϋ*x+d҈9kN3+=mӖ4-~Sf:5Ԟ2U1_(9cy(6 O}&'MiS*ֱf=+ZӪֵn}+\*׹ҵf׷U{}ydPF ?܌Q!o")GMU2BpT`g\.=]Gp!\q[ h*pe*d#ld&!$k"{U!W}%k_{C`6$ʕ `!"1af[lZ_>⻬mV+R8e\p6A[Vl[  vЄ>J"JG0f49؉vLņhB!p{C8P$2t/_l&SoFF1 = C VRX)|_ _- A1"_FEI3!2Q@)8N)TM .B"D$  +D`e.- . t2p 2PA*B V6啡VaQ^1 ɡ"`Y!]!za6 ,^ 0ԡa@@(P`(50)..,!ߡ%> b] 280^)ߥ@)0`-ڢ%2"36*(0b/ /vc 5Z#666b߁8V"M""b"7c8c"9A=$-4AA6@#1$ T#)m߹#*c 8:=GH$2ЄDF$0P!E=cF#>AL^"9:`9B$EdE#a$IA*B*cD:O$LMe?E,] B$BvA]CXFXMeX%Z][[\%]\eA%^ڥ[$eZ][`e]f]&_6fa &E0M<MHOdLM bFc%ccii^klflmBfn¦n.fm&opfjo&mY\esҎd&ts$qr&r.4"9JR!gքg:e8$PbgvΦwv "N`y5Izgb(#w~m|&g%%f!v0'hMJithq'}EU-lA,$Ib^Y$hߝ r7"c2፪h(߽ei>#D:9)68`6%#8>MA.B ޔJeRb%Zd!'ߋ6eP`1B>_zd:>9*2Pa i8$"5j$$a("' 2DAi0"%-Fm_0@`1V\m v-f< (O֚W׮me(M֩ܲ->تB7q-܅iKd0zY:E."6F'n_pЂn蒮]8Al̃]ʮ.//&/,]ԅO8O4/RNIXLT/rE~oJJ>Do/I/oœ%Il E/\H\pPFo*2:0oAG kwo?oSWc[o ҉b[_ G Gp p 3 sooMp pR S(3:q@qBz ;oE t .]L.[n Dq D1qK1>q!23#?"2#12N.!2oC2'#2D'Gr'r('/)72)r*2%2 2$r)w..*1.3/2-21#1$'s0C'$a& &r3#;s63)r%%'r&*ws0//3:˲03< O5 5s;6+ CJ t@AEB0A@/4Ut1K1DEFsqC_4otXWU4RF70xCKtTȴSYtM/EN+N4N;CtCTA#P3qP3RcStTT35POPCuQW5QxuUp55bSo5Z'uZ/Z5[Ƶ\5] Mt[]uX$``6>)ݴ4!c1cW0-D`eo)dwph] <nĜ$̏ %[>D;>o0l}?fڻ=ֻ}nPLЇ]#ꋈǖ {ݷh(i<};<tձgv߽w?<'>=~ ],b/t0 P 5D$yh z@. ]6jfhhv9nUX!E3VICCQ#cgbFsW^Y#nh~\0,2_RkNI&ߘ+K 3%|߈ 㙌K]|D21OE}t<&c93v3+TLRV1TCʨǖl 1 [VE]4=O4\ PBTFAQhM[8_6Ic%50~cduZ6 iV6[7 qy\tPXPf+\z`ӭZ鋔a~; Jf n`~fsE8ZS/) Zb1Y4 p lN^PqcZCwߗkY9g愫Nl_񘍐n߄&Z碁kw-֨Y3"ꩧ^i]"K{k֗ш%=)APPz\EhRƒZ,H16;]N9TEhew~!m7>tK@Uu_387qޓ=уb+Fn?NΌ4rIzl\msgU~-&$ ~p! 8" l`j?i~'e`%a@OHBtBq?e08T hp7ԡ 6> ͇ :Z>ȯM5dbq8B0T$:7M|˸F28[QB9oܡ!K^u 8䤮uI6A(!\j&sˡgX;ݬ4 ĥ۲P Hghyaop.{fqMAjl礔G~g۩neѵ6kq(&a}P7Xح-&MV2AyfPz ah,J;!jhQN喌v!ӥiLG4|| %5JV0+M}tk BYC:W]%+i٢F: g Nđm#"Z3,&NߜRseh ^V.M/j`Aسf$+2N~3`5YlT֭Rl_1v;Ke:fMYcaK?Br\6@z-c659];2uInLJwuyۜs,:ϫ>, xߛwꩯ}["wY/ڋi.n c1a oALnb!LE<-v#{a! c=W *񑑜d%/"Lj|c#&!Uled]rÄSf,0YM }rs8g9w3zs&9хs4O2o\iMoӝM񌧹tﹻםxAB^ 3~O8WfS_m~o/C~݀|Ny[Q3=++r#A6T> p3"!6qs 3/)2"S237s>>>3?s???/@ @/+c@ )9tB+zEz@ T<)=0vDc:`!<Z<@ FE]t!nE_!#DTh!#z "::!RE7 V6@0ǀ̌Bt!8464,t!-DRHA@E_z\@ :a$t U Q /B!޴t!MU 42A D- @ OĀ tLa!$!3#F/!,s0Nmh@ h$aXzt!QY"/;pRgsr"B0_ 8"U#3LcA@T\3/'SAuu[/S85X$AO_/fgjf,gix|Vv"\-!h S]!R7iZ0{:[ٟ[s,L3vCVVYp2&@XXnYY+ܫC1ǀ@(M!,%a7ukz{lz:YZ Y 3~ܖTrr% w1s?QT HTתʯ"xsax!,y5#"<5ĵW=anqZa -T wyzbz!Jm'Fet|;!}-Y5{qY7]yŜ-: .G5=Y]a=e}imq Ֆ79h%`A׉؍ؑ=ٕ}ٙٝ١=ڥ}کڭڱ=۵}۹۽=}ɽ=}ٽ=}=}>~ )h` w^!$)6"~+~cBāE~6sM5U44^J~a4^.!|,(('&#    '''''''')+, - - /4565540---./135678;?"H%L'M*M+M+M(M'M'L'K'J'J'I'F'F(F(F(F(F(F(F(F(F(F(F(F(F(F(F)G *E%+<).5+1.,1-,1-,1/-24.2`??_@?^E@]JB]JDbIFfHJdGN_ArO22222222222212220H0v15;:>@??@A@@????@@@@@@@@@AGOZ`ipzǎ•gYMOPRRRRV{ԽǿƹǸ˾H*\ȰÇ#JHŋ3jȱǏ  Iɓ(S\ɲ˗0cʜI͛8ɳϟ@ JѣHjTӧPJJիXe3ׯ`ÊKٰ]u*L{۷pʝK7jZ˷߿)^+^̸cz ʵ˘3k)<ܹӨS&]4hcd۸s}w쭬ٲȓ+/ڸMڰuk:Ⱦkν;)ѺZl8yGڞz_~}%_{y97߁& JgXCIǟABaxu靋`ujdщ]F*b)њ+ff:쭃:)F+UM1$ gJ*lDJ)ik線29K:dh{K,O"2nl>0G,4 d$JS Gd}aF`(Z =e',!8pua4.W'YΙH'm$Bק*-Ԉm0\w}"a֭^mסlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣>\_YAxȇ"@jĐH$ bHL$dA2yL&䒜$(/9NR,e*7YIUH' 2JR*'d(Hh(S,1_T&-U L&,yʃD&T ^^dDe7)MpҕT0cڳe0ЍtKZu.vzxK^솷M/zϫ׼|7~ӛ׼nK.ƒ#˂0's0hBX`4"~}c,cҸ1۫*ĈE,.+Ȃ`p&bHr )Xɭ1&Ow^~1[:z[XtynR WfVn2"AD' ƲEю.#ISɅ)\ "HE[Z"mш Męq^Y\!nO ]U'տ^u|gz5tS"uA,"^İa7Љ.tr-]l{>7}}tν- .YDkH$7x\+<  }6-y,<`{0r=s<7ws\N > /| ;qgy?_1t+=I_:sޞGU2Օu B 6Ƀp1;̗ۅk]agz;ߓx;pW!w//? P^ys^?}7էw=UO'=1o{G>~iݷAXrכ_/ǟ' ~"˸k]'~z烿y|7Чde\W guZ~z(Xmvfh;ws!Xs#8s%Xq'n\BwoutS6lM\J0pN]2j4pp9uo]&p?\`h'c D_WfYH^)n]nE 6dup0tՃger5u:xkإ4hCޥ]\P \i>fUXj7her&bΰbߦhz(i(^_xnXnXl=v}i P w\uA\1k舴tĠ h(]gXf_Ʌ uv \ e`e "hfƍ`6v荾f `ՈŎEp Fh놏㦎ȏ)Fi%;.&iɐ5fP8,gIx\u["93$2̠HpQDZ[%y"[ ZT/M%ѐT0Y<)OX"5rYR\ MXtaRq Yt[zՒRɔ Xv5MoAy'𖧕ZhEYII>aJ~WqV1 PU^UTT_ŗ?AiVGeUiISSI\sU[n嚿ET铁ŖX,y L8iI-[)zNiX KEyyVjᒳ[8iuJ)6[Yyw >s . @ O ! p  0\TrT. J O a` jС|0 @bRo (p`o* 1z01"@-auag!wfeW zj /UjW0-ކIHfqge0Hm\vt4P0H0I"ͰHЕJ1s* ZJ@Z*uڣ<*\ɒV9# 4**p 1B *THT T1 e @ P\1 AJ?Izկ;_I{I Kڰ)[i +۱ &[%{*+.0&;4 6{:<۳ 4HoY@?L۰N RT[{X;Z۵^#'՘e Qa۱`ڶnqsp[~yx;{+z[Wt~}뷉۴  'j@f$rGk1{} {+xK^;HK 'I)U4I+EKE `;0ʛ[SkԺ0ѻѼ|Pы+ [r; ۽[雺e;H{; Iko| L;+[[;V,l )+2<,klLQeB;<-/$Z;SkW|U[]_|Yܯف;pRakm\o,qs̴uw yܲBP{|L첋c 챑#+ ɛLƝ̯,|쳕Llɯ ,˵Z̷|H˧Ìl̷\˪̞LM ]p M\a@1\fz<Ae1?P 8:/=!& [ ЫaPg.|@g|yH) O@ ! 1:! ,!*~9z$Rk>HTFG!pֆĤ!`MIZک,1HIZp苞I 4NdΩ~nIjYI[:&dԥnNF2뙄 h D‹6Q.8zZzM~^ qSn^\V_N/mM"L:#?bHCIց1NP=}Y61-/j* D 3X{4FHK?@^NĊJ/LN?0/^]IM(.Zot&2*N@ V }Ь YA|Xm[V  A!Ժ@ lMϝꡢO>Aӧߚoߧ?AO`e']տoәeeݥDaoO տ|`ib uf ϮeS2oG@ DPB >QD-^ĘQF=~RH%MDRJ-]SL5męSN=}:3РE"O`hK>UTU^E8T+Q>[ j VXZ|ӌhgݾW\@:0ײ][nZB FXbƍrw/Acvlд=ZGM> 9r`^%XL@S4h0dyPC(ԚQI'ԡ8:W|RN>VσA䣑Eth 4YMKmV[a[kUJ4Xa֭K:SVyS!E5TSA &uHCmN9=\e\ʍ]ծRb787ejSNO,R!']HJ#S> h BdSV,2fڲe< YYЂfHОݔX |X~9iul(zZ,(H+ zV$ե;YlF;mfm߆;n离n۪=;B\ O~ EHvŋ'R̗u%z,bԥ/[cN8-Qmj9㼬F6*CFGC!p>q"//ؒ.E$K '?$N䐇][\7/$'W2l^[}$)ƴ,QiLaH}̛W I$@ 2%|%Bl Wa1Ӊsv[tVDSs eMs5\]6M.} ?й0phD%:QVԢhF5њGRԤ'EiJURt-iLe:Ә7iN[jSԧ=O:ԝըGEiPT*OS:՝>dR,8 vXVUeE+Ikm[ ׭uvūZWH(B h@C}h`X,HV!NEJu#!Zҗ*d) [C0)g=HLhCZ+'Bk_~֣mnu+ \OEH#;ُ׵lqik[xvu.tڏ^"Xmvg{\zAoSK]:5nA&2w } T={&vhcMjYj >.gD' M@,R! b]Ez?J" (V1iA҂3q#c/WhO:"٣4 &7٦,.TI x]qy<91Hf.7ɲr,h;WkHeA(7MD 4|$+د~jK0s;HկuQfo`bX1&!"}4KkNأffPK HiS^Qf3ilp?h! Rx{fw= hl'v{ۣN|.8 g~QULxCEYַvb}TUm=nAx4;6xŃrzd ~ܚoHxzK B8{[BQ<=핻xt6|}A:{Op>S>?w? ~?_p]pۏzC}_l3)?ÿ=?k @y=Dв?K@?\>(?|c tA dAD=<D@#)|쫯3H >63>tT|s\<0[:-34A-lBX:i[>'c4\Ïjo?8 C4TCÜ=C>D0BLIDD8ñ&;ră EK<)-)TdQ4ʓBY78,f[-K(KZ9*Xp-3.c?C(\0lĊB7FF;:C+kS_mi({JbJ JJ1cI7N#TH{$'60 llJ3KJĉd*BIbPI'I#K;JW+ƜLЬ),T?ӄ)LMd)ք<**μM t5MߜM̩$N:Λ24I攩tNMtռ΢N O,Oڤʹt)L$NNMNL*NLϏt55 RP *{pP{B i `Ra 7 %M_Q%PQm$cixQQYq "uz Q(%m<; '"p>z% %e.ͨMP j&R=4e%1]aPʼn`>x5SJ֡<@-B5CEDUEeFuGHI%qЁ89O@Ja*,.2i}Nx7PUy<>ABEa]7fe]bP(IU9$Q֡P = ^!WsQWte2*UbdYɘgV)>Hjlٖ3V5 ܐ4R;z#?ҪXx%! h%~׀  Yy O`qu֖U]`;WT)bEٲfT>fu١-LցYIY#I%ڬy|x`ծڴU[Եu۷۸۹ۺ[Bj׽۾Q5Ee\hDž\Ǎe\ťUW[}[5]9-We]m]ؕ]ʥڍ]Å  Ź]EUu^^υv쵕݊PM\Um_e]]\_[5%^ .`=`M`]`m`_a```}-Hf ߉}RxR>n a$$%Nb b+#~b>a(nb(Vb.b' fX,b-.,0b1b1v1-fb3^c=26>c698~Ac8c4~x9Rd-B&@>dFFd ~d/vd2:.d;dLcSdNeCfeQneB>c?OeO~e9V^% 5N_b\c>fd&F_f._g`e^fiX h^n^oFaj.fqv_r.gsn't_uS>fvzF {g|v]}g~g~ ^^F^.^]]&h{.sFh|h i܋ҒV6m}iӍiҝi5h^ifF%6Fj: X'qTȒ%.uy6VխցePM(LPdX=jN 8'q-FX2kXY&F>Yc Gj ]욽E)mLlM`~NS@@zAFPP~ m֚V mXHQWm]n (( n9nhঊPeyenjI3qy타Von>Q@RkemqQi 2h9.ue), \V %Y/U$L 1q1Y MHM Є&' ׊p' @FXbyYٝp u07 nЄeF.;5'p^nZs2 3oCG_n^R@r t>t6Y>W C@1RFSp[u[uE ڄu([!r_ubuv'Ybt_opq'r7sGwugvwtuyoxwr$}/͇)Y&Ujw α}~B c`lޞtgxIw|whE&4~ux!fzSQw'a hoOWny' R|j%yv?y~UkrXoUAy*"DR>sxy>4ξo'zb$ugptMt7ZjS}Yzf`Yxi'xl7[^}p2|еЗOO/TS7gRWQ毈u~W {`7&jSWklOk"SZ`oǮ̌F s  Z8 [H!F! #Ȑ"Gg$ʔx "@$ A !'Ns UBT>Ng$!")ԨRRj驡9e *E"U*ڴjײm-\^%|B+Dݝ=x+hY>8$ld4.Ċ3n wf͛"պpѤ|)J)GHTB u|E ]B?"pFӄ&7pYC& kڶ;wҧS>WaV>_?\bn9[~9k9{9衋>:饛~:ꩫ:UGK+ 6;*HSD%.|羻HiW 0J։WV=2<視_|T{:%z}=_|O~P1H,oD0AУ(/u ؼLzo`W )|8P rА khCΰ7\H}CP!A )1=D'AW`(L^ 1d^MߙO{/Nz##7Qe̠:Yd 1.I>yN`Lʒ$BHB'gJ2+>x. 1pȀB̉v.D>ӄ5GL3A=&Cδ?A2T-DPG ISVL|H!8I 1)Ґ0\󪐘s3j'1i A,5 acuU|k*Qft!aUn4DX-[*dr+G)MaS-yWե$F:C!;`?rP#!-.ˇ&s g[X"H h+!> Fer\W:B-3JR۽M(J5+2gc};:,oBCqtW=5!\f\ӡ3o_W, 1Q&x$FpGgTE*ź3%D+*6!D,iCܰB Y.u"kU"k6B-\mc[|;q5},e#BA!8U-T[ 5V>X5GcWEUU$"TD3B^߼m1z{+eC 9ArQ2u'> *lx5jcHR>Րu5<vIҞ~6q 6ms6-q>7t8n*;f\ )'wy$I& {(A9N7D%Ko71{L>>|m!9)C<.(_yH-2!'hmȘk0,a*7Gq-W`yN_8>t_EI@7)GyוhwP0zI.p@Ih3+EI_oŲB|5 95vP$!??4>j=o-xG6?;1I :U pAj8'ٟMȟ`!. 6> FN V^ fn`>$D4 05Ѿ _5Ӂ `,0 ٠qVK j:K{xS0xZ9M%MV^E31aQaPAPj͠""&"."#6#>"$F$N"%rO9%f&n"'v'~b (")""**^"ҭ+"(","-b-".b.,"/b)"0c&J' #1&&"268#4n"3F3N#5*5^#1f6#7v#/~#8¢T0 L#5#;֢;c/c4#==ޣ*>"?c0.#,LtB@?.d,:7B$CJd8R@Zd.6$FFcT )dGF3$9J$>K>Ld,~dA MIOdPPdQQdRvR:$ANA > Q -i%`e5peV*UNIX&V%W%Y%[Zƥ[%Lܥ\ƥW_^ y&]%_&&`[c&"%e:&^@ere_6f2]Z&dc bF&hngvf^dfi&lvd&m&f\&gmfh`%a'bfjfffr.&Uq Qqqzir6us 'umzv.&r'z&Td1R{dO~bSb|Zd}R}Jd~B~:e|#NB%#->P.d dcc^T_#cc㈂cvjcjh⊶cJcTc>dzvbBVc rd"iߒb2铦dϔbb4^)Vi6b#.ᘆi26F.瓢0iDiDV)Ż4(d!C DADMB&XFC<+DTp:Lj qI $EXP) AC@DؐjBdF (_L@4*NO`P{\`h,fjP^D_Yk:d*enFg|JqܵTltkntj Hx |ƺ& iI ,|͠`H H+4+BP ɆNB,l諎'Xɕd Pɫ@rQV`IƜɌLJlhDΚ - G fVƌLG4̦ FŵlLsxNPNؒ r̐x 3P* $X| DŽmBBE2 %܆-LBymBM$FBҽ|D:BtnЄ Gx)..//&2 &D>F"jDI/!JReNv(}"➎oL/"bU$&퟾S4>~~7'???W?'kt,w՛'@t MVB,,UG\tԂq5b'N|nCT3Hr2ku"E:^ڌ[vڐo_[*os8O8_=1rwF=+yc˞W4z0iX k!nSh߫hf hBONv>Ґ*A%)[$pÂDA>* p(0EYte|&[BUC lG%ȅ%tF#2+ 15d P+U*EʂWPCx։@sM8 ]њF%-F,IH5S9?}PtBIUoTZU^5XUYme/WOwTP,\X=VMe5cUg.i5`*lV[o7a]iq]y߁^/O^o硏6^{_'dNQ.%^Yerzdu4ΜN3!5~P UȂwD0I42-Z:AY1 1$ 5yNyk84$.qmJd%mE+fXVpІKwz Be4CBA-/#ccNpqfG鱎#9QG:V\̡ MydDC j`6FQQ]] lzUZU*%kYQzVtLmkO]Nxbp#i`_ַ۪~T4P >+a3XȚtU,L%{وfV< &'"K,L}2"d#2QNƶƆնXmZqC9ЦX*0ղMkc\׶b.|0(w7{{~vl;j۶r5y{Xwɍm)hc H'j["\q N[*aPxÇQLHGxp%wXwᰋ=b&0moM_z$6q)0lFMh uڞ-l'ϡu,JD_m@-bv݆*7Qfmv-ﶵ[+׹O31xG3m ]֖x)~{kd?ِ;";nvEyrT&6aq3Ϲ}^vY?/Ŋ*Lӝ J ձuo@*M ًfW܂pWdz+ Դ%iRgҁxQ|Sֺ{ÿ=zXEH\ L +,0 "O (hV|)FӦv@% !.Fp~^1e bpFH.x0">ܻb|{h?S.^ԏF(.`j jpfo@.$.Kr"d.d IHsj3??73@>k@ @Uf?tAAA, j!a0BTBIt0A$!=@nFEn@*D5D''RtB!z"@,rF1@hxD3KgoTP,.!u_/`O4t;=G/RҰ oi=szjq&!OMo 7Pу<0TC A}bSDB\@ȏ+pQGkp@pN!.8@AĒ /S@ Y?U5tdfPF"SQm4q6ip=f]q1? Aq ^QQm_%Qm()'i LP"dQ/n5]3b&wBXT!QQ/^Vtk=G0$D#v/mQcsJFFhYU2hMMoOOMu Mt` TU6"ClP B$+')Aj Mrti.J/D7 3\.h!RIudvY2}7fP&Nuo5YsWyuFy7jguV쪷{{wffxwtw|#5ze|ՁW|zU}T|hטWv_wwE~7R1"7wW&mrk9FTˢ^@Pl;p}=8yx/787{EVW1mFYiWs0xF_A&+V:@x+7{mPٖ}E}Wŀ7nWUU)kauE5b 9y)! )-195y9ْ'A9EyCIQ9;ٔU]YeyWm19qymYy]ٗyW]6Y9A39)9Y899řɹ{] @Aѹyw9iyY? b]c!.@H!" p!,9ցO*Yٟ9ZB"V. Rab *Y)`]:Arz?=:OyzKYّa:.R h!ϑ 9299Z?6!Say:ڑ:a jژ}Zz:+9%;)hcB ک9+"#(y91[C)M#9  9 {G)E!1۹+%[{9!)aa Yk !Oӻ7 jdm{[ ]!   ;[! !/ \/%b#<|@|1;G|ASUƒKe\Ek[u]<}\Y|s|9|9ƃ|{q<ʑo|W<ɛęʭƏȝ;zYE½;w[ɵc\|ż G<<˯ }Q<ѱ!<)E <M[#λ\<c[sw]{'!Y!yo5׉ڃ:7[5;w۞ߥto=ڠ9R#! !WS>ڑ-暒i 3Z] 䧱Y?CGK~@W9NX|ԃ 4QQana7]a!sM}ޭޱ1 ٳ^~~׾wߞY=>ߵk:Ӟ)- xa׈{ӏ1y xIifyI5]`{X) AIaݸ8k?KxXc3e?sN@aNH)Hana~b"Hb&b*b.c2Hc6ވc:c>dB+B$'#P4% eR~}gpr 1@!) +=: D"Ȕvމgzg~ hJhh.hۣ> iNJi^ini~ jJjjj kJkފkk lKll.l> mNKm^mnm~ nKn枋nn oλ2Q?˯2Kp 2?is8fp7al:]h;U`;NX:GO7FM5GL8HK@LGKOETNFZIC]C@_A@_@?^??_>>`==b<>e?@gABhCFkDGlCIn?Ou308114011/2/.1.-1/,0/*01'.5#+< )B(F(F(F(F(F)H*J*K*L*M+M+M(M+M+L*J*G#*D%*C$*B!'B'C'E(E'E(F'E'E'E'E'E86530.--.04555542 1-,*((((()))((''&&'''&&&'''''(''%#$&'11222222222222?4b;t?yA|@?>>:;=@@BCBA@????@@@@@@@EsϜؤ૥ò໲ɳ±d9~3t3u5}:AFJMQTg՞c H*\ȰÇ#JHŋ3jȱǏ  Iɓ(S\ɲ˗0cʜI͛8cɳϟ@ JѣHjTӧPJJիXe<3ׯ`ÊKٰ]u*L{۷pʝK7jZ˷߿)^+^̸cz ʵ˘3k)<ܹӨS&]4hcd۸s}w쭬ٲȓ+/ڸMڰuk:Ⱦkν;[7t>[2S.N.ス?} 79^^g&xRe&]xyf~uʄqxay! h 2vjcMcՍЌ H aƟ;H&ypߍy0:`X>bZ1%JKL(o\lYr8a3%BTV&CadCs"WAp~y裐v瘔pF$]jףNjiNuF*무%WYP郗jvjzjta eT6;첢j:%ey#9%]EkxZ}UH+I|t k&k% )1owI60FDG,5o Հ/>g|<mVk4PGJtcJӧ|2c&2d-fiIfu5 x#g|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣> IBL"Yiᑐ$$c!JZ̤&7N^ (GIJQL*=yUl%,gIKV̤,s[|/ILV>,2Ce:S|4+iZS ռ4Mgr&8)qֲLgG D\QX Tc, ;3hg kh,@6ig(bO0D4*`2V٠:JҒ(MJW8/LgJӚ8ͩNwZS@ PӡHiQԦ2uNT:ժZPVխz]XuhYVp[J׉=ū^c׾uE_:Z"(D`Rka#R,W5r6,X%j] C$ʇ]@`01vQУ3p!w|6 *DFa  ӈ\ *t+\u֥jv+]`-0V; (bx!|Δx Ԡ,v "!.ݧ+צ>0M6x ~p% Z ;UPMw+KSwAb(fAwjvSuc#5r`zX N T)oAQ,@$QB;p cȕN5@yts\™ u@RA $t AYц=HOі^t}G;ѐ洤5eQ_әtMiTԘ~CMYǚַYjYםk\أ^5Gj[;fGe:ٻ3gn3w3iW&a$M4,gBؾjl?0\e{8/('ut⫞mq>7+~G!DЅ9e.c3?Fvu] 7ī5ae#)Y0?qQ;zֵV]NN]"UD_T٥E; w8{ždCH5l Es\ƋY%SDw`yFKk@B(WEHG8I(KM5N 1P` & T0Z^( ` [!B J  cXq3kxm Pthdhl@H(OP؇Y]8^8[ ?I&0aBw7Qh_Baa^ a Oa3O C Qu@ WP | e gg%p`^x^0戎X  )vFe1 ȏi ieяEэH^ubC "y $ِ&4TQ`>9i@q N0v Ɏ592 2Iru(tf 9&biȔ8_)a E^)hH mpIrIu9siyIY J b(b0+f)9>YrI>9Af 1P1`gF1HPq } ` W& ɖpy))Ɉ?q9j)IY{Gp.ue^1x){=YYXN@Ѝ.š:D |uq 7SIG /a#*2p' S)JbhT1*4pr[*ߵRPv%e(J8 b:p.: \ [@jB_+vJZiJ2gkmFmmoZĐdU2ڤ `[Of;ɍ8uqt9Y٨Y*y'Y yZ Izz8ْѪ)٧W]J aON8hOYI"nk 55Q 2@mG%w9w**Ō)l0K9劗皮 ٯJ:kwȭii5ګjɱ'[ʋAʬ˚=q^AGJ*9PHRRfƅy̨v@zg⨅&TkwYh Ppj۵I&e{>k{myoKI1tP˴tap9K,FضM۷zy8Wɓx ^ [!UoAnamgfeC;[{1DfOż;2U*:f.E~TWVU!'E|Koyvq  l:Zx؋CPCK7 qmuM"j) ?Q S*QIjIF)h'Q5Qr(s/p),DmGLSϩ*cmLj[YZĚ6iMN4TnL^pTM\P.d^iJfm^JVZNEnM I@ ԰Jh  DIhP Z h`I1P T.脮 ZID ƐTI9`Q> J tcnHDw I艎^IZ >Ǟ쑴D@ ^~뉾Ā.*ҞdIx~Z0~>PBt~ HE NP  H44@dZ"IZP0*>^~J@9?DZW~J @#@/*FXOZ]!OCoW/Jk`Ih s_$OwoIToUOJ68I%rF_GHpZp oIuH@h_IaI;/I /IHH"/90گ _IOǿI/͏E@ DP!X >QĂ )W-r wMJRJpAi:x'/ SG-9 t'2Io4gA y{haC@A]XjAYAr V܃dBswb-5ܯt 3Wq"lm‡bmc-0^<h/ AZ@EVs1Ɲ[S?&[ I&[Wr`̙5w]TEzE-P^׮% )|r|yӧ\>xrXpE7=H P̫O _%'"QBe/?`2d+8*@l \t@(^'6O2 BM0GW|/a?jDLA&Uln _,< p~tR 2H̬0r%<(;ӌr'PptP ODD4Q3GI>S# K8nGH|JME%F&-.B_ y,jI|-8r5 o(^*h_ `ҒÅe g{Vi#"XeY-Eh[axLuj]ִ^[Y{}5C͈_^Y-$a3an"R=nW  yɉ`Q@1:yUcB9jy*4zIh" fx;wչjΎf휆:"#haVle&3+2:FHႂ+`yc:"oryNg`@fjW,ch_f/*\kwѿue}- ^v'Ԃnj߁ϬBc y緻'm:^3^~×~X?g]K.ѝ0?ٯ,U+ŏ a̒>00# AuS킒 |]Hǒ܆V-i Ɛ..aqpX.L!AzCQK5$",#.N UB(榉Ulx-n1a^Lh1>9q*1,btc9*=Rq[cG*rbl#"2?2#$(3Ғd&5INvғe(E9JRҔDe*UJVҕe,e9KZҖe.uK^җf09LbJPk s4f49MjVӚf6Mnҙ* gvӜDg:չNvӝ7SNrӞg>O~ӟ'9 EhBPt.ԡmC%sNԢ-hF5тhJB##-II*(,UiJ_s0MsR¥ 3ijduk]񦖼-yZukK3 , | F~Zzc5;^(qQa(i` ñ,ֆČ5-]P(` 4qBypQϜA cX]cZ '}j)UN.eͲ 3w%B!gC2{'CxH!2Y~2,):PJhF+˘2L ,P8ecxf A|mvg; I +)ak]׷VIk'z&vMT^nvJ-mZ6vO}l5ڱ6=gS>we]ev7Lq-nl[ȶLokC.n7\vxO}ojOheKw4ďne'W8U^q'˓E/}1[(jlx-t\wP{zЍNqU#0Q=ۭs5t۶fwyBk^2݌sxaﰝihwLQ,xxW1Ox3^^V(}鄦Yb(AșȲRcHFYdy=nc?k|k=ʋȺ+tg֠+r6<|'+nDs38Bv}ط@ʾY>x?g/__k9@*޺?tc@zC A2XH= DTA*x&4A%Cl+$"4#$ D&t'³(*Q!./D:B243P5d6D78̪6C6;<ܨ:C=?Dw: B4CACDdFtD^RD7|Im,#D;\.CC # 0c@xD4 9E1ET,XTh`%E`AW>bKl $s>bEQ$E0x](FE0{k̥L\d4i:[p`1ر1mpWK G]XG䁖X`%XY*Ś'eDeHt%udGwL0K d,]%qTÖ @hH4@:d5K 7SJ~3Iyl dʡ\hhJHe ^ V 05 ÔpQQ+XR0Ӗ('=eY-Y3-!s}]*LqUj|e L9݅߻FNUOPOK y8cM֔xdU6&%\6%Z?Z\]^_`a&b6f BzS4ecbXffMmާf=,N0cn&t:H rfg{C'Ȕq7T OyX 7tIt??HtN/@PGKtRQ'tLtxZ! {w֒_x AlJH1z2iyӿC*ا{G{ӧ}o}jҟ߯}i}}~}oh~]=0߇dRel?Ё~r~jXPg^@N~X?QG*h`paÁ 'Rh"ƌ7r#Ȑ"G,i$ʔ*Wl%̘-Pc5;2w3‹2$j џJ2m)ԨRRjal٢My+XЁ+ ]fϦ%kAoۮ.޼zT.c҃G&61Ȓ'Sl(C*XP64ԪW\Z6jjZ^PtŠE%6]7‡/n8ʗ3o9ҧSn:ڷs;Ǔ/o<׳o=|`?i`jm~7} H }@Ѓ.XPwxa] ahb򥟇XCM46W*XA53hXAVhЄ 鈡8~c$<6 b٘N W;=)eR*9&|)!9$hڹU%j[^y%XxY杄 zWPM#Jzh!Q%xiJ :*ZT䪛Z(gR)NY_FCMA0??;,;lt,"[,6߲JVe2{n[Pɦ{Wnf^. &WhU5J# ?AIF h°ڙGk^]hASzZ'e2.A1s#61jZjP.8sUE3p4i5XJxљ=Z5+MlW<}"LWo|)2Gg]}7.]y7 >8~8+8;8K>9[ZTl{9>`A:ꩫT:[V 뾻p 5H><ţ6zB<(jXc[TgV`=?>>髿>>??/ؿ?g6V@TX ‡]@`Y1 CL zt 0J#3h bj(XC@Pd U C̠ !`An#-dX80TdT\&5pH] P!|aQ\E Ś)NXŃo " ЍAȐ]A!q*)M-$HH,e,4ɲQr䓗d*-yAA$GP8!BFRR ( JN2,o#bPl *JJeH01H;=dI[F4@z iဢ$bL ṕ~f\@Jxbg>GO|Ay 'Bщha0 \&aR4%AsZ2r)NcX .QijSur0b<$"B_ w!SJo4Mu֦t#>%Y LұX#Cֱs}c03~0#, So.aҮ 44"2"'v)BbdxƄ)@TU b"C b2h|%(]2h~ldvpjc%'C&r\b-S/ԙ.g٩܋͚'b1K)OF%*rY@r|19.t^R+3D.eAidt 烐~6-ͲPї6u\J-iUS/Uoi(͙N1c:,ͅ33ާrq7e&׈!k0'&.370Dv{f:܅6͜m05fwkMyӻ7}7ӽ?8C" pkS|yF&^>s\ϓx)r||x-\'U 44pfS}.t] cГ3N:ԣ.V:֯s^:.[/;Ӯl;^v˽v;ϝ~;]? }#cW<#vKaՁ_X& 4yW'}QzX##@k 0.v CǨQ{#-de^IFINq4i p$ @ C!a0,&T ?Pֱ99eb& `fDf~fAefhv`ޡa"(&WzLfJf,&gz&jirmiN.W2c.#^r]m&pnsij"#O6&6.'mF XuFguN'yNp/!w6er#xb]sJgn^]zy֡%gj'~z~(xZf(o gp~f(*nVh~(oJsNhB'Fk:\Φ^Vf(zG`_0vcՂ//ANo&>/N Y:ũe|\dd嬨oJmo/6QV\/00/6?0G0G0W;_og0070 0.0 pPl 0 ð Ӱ ߰7F,00qO1'17B O10s_ g0GnI E711 1qp/1 c #/ @o +D ='!0'2o.4\(<23 /%Kp&_&oq'0&J' S1@d+B!(#-0bm-r 0q11#s2p{: H,2#67Fm37wsk2s21s1K>s d3+SE]s"7J7s'sWH(/Z (B/@A$tD;@@AHED[tAP41BCFtFItFst,x4RB4tEtGktJ4KKctNH5t4O4N4ER5SNuLQuI4RtO3J+uWC5L7Lc5WNO?uSK[T; Ojb^c.L3>KE -[OWuXOW#E?JaG5[7X5\Oa V_\unSWvR6Z68݆Df9[0@jrkkrlS<4@Ԕ>Y3`csv@7h/w &;w&C%K7#S>+>sno 5o3ssy#FhLdbYw-7w6wp7- m*WhnfnO<Է~/A/~qqwqk1)spGaCx;x󋯰:x Wo8D83x0'+q/qW1;'9i9@AI `D UAZ2gW@yH A0 Qoɑќם繞+!D(\:rܢNDHz6qM2TmsԨxLJH H+cr J|3MY:rEhNS/EH]WPM/;ÚWju?;;ǻj;y Xۻj..BW-P!DS: C;܀A C@߀BT ܆?q'1,@+VX F%4aC!F8` t0CbF/fHdI'J@%b`*#&. GPL< 1 eQ'"viSOF:jUWfպ*[ oA+2 :ǣw F7bkX7nbX0Ä VTO!Gv WQ ׶}˰,c)%tiӧQVuj_ {ph6S;J[/7:dqBr?n[ R/ :pwr;҄Kc 02Ctɘ Ł` a,ek{~x+8A01CH41 .Xd JLe!!> T%‚"ql'R)QEܐv24i/R1,3LS5Y،S9;37TH? TA -|EmG!TI)K1TM9] k;:-SM`ZWS`[XFI _OWZ-cu:bm֪OCF gZTikSn WܩvsK?tm]êycW{Wku}er] .VU 2 n!X)1X9AYI.QNY冴[+]kI Fcxz9"zY1OZ6e8rij&]dke] \]h 2%BRĂ(nov>J ȃ`ŰH24$6[Z(:ꭣwX՝*}4׉RtA> O }6 \H8/hb0 /oXl`뎺 >Yi\'\DkgH' -oiħfǔ/\Z|;]!jAn!_O kof`ܠyp!oR+ї:)Y Ȇ("aR:ؙ~! x51kbG,0QT 2ģ =$p-6F=l_IP=G)($H !⁂K2P&8 O|2 xPH #Y#$FFR`t%?JKn2dAEO* ͒).,!9 NZA` X"A1d&DINFs!Ѥf @aE A|p!K,IubKI#=sgCA Te۰hsO{ AI{ ` ;R i8a oAb%6!+EacϘ5q,={c Eαd%'Kv e)OMle,oYZ\{e1͜Ym1g-YmxQ2Yfsg@g$ jT.4+GZSt|iL;yhKz P@-ZLbB1=ܱy0ofMȝLz  Q/cWz>=IdҸph-ڸuw![?&w@ Pq1j-P6<Ø T,Սc';{ǸP1.P~z $6-/Un9Y\-yM`ea5@G9LcØZ0:1rHH0 jL_1LXU=&Ngq]tqB-,thՕ+Z; eDUρY|y3<$(Š/mtlG{Mb #[՗c!_}e}_~ZؽW~{/w著|C/}?G~uO~?_?#G?/P O`z.&n,V,` oP/`/#z/fYŏ/>C^1YOQ spo~*V &Xza 3/ZPA$N.@嚎ZN   FmDn o MMwtF aMPn ۴ My {MŀaDMjV,W, A<1-(@f  |25JbѺ ZmQQ<@M|!! &ŒDŽ,vD&.Ƥ&xW,bQJ1 2RR! i 1M"+"l ׬!R#g"9#]$uL$G$$O$12$Q2[^&c#I.&e%iLr%;&ϰ%sςR }r(/(R%Ql)))%lc*#k(≶fNbA *r?RkɈ" I,r5ҥ`+`T/SR'-Ȉ!a/S2ɔ# 2)-330!.U*,*@3Is3ag"3@4a*„|䪮+6q37us7y7}738s8O6+Q39s9893:E::ӝ3;8;3<9ų<<3==ճ=gK=39s>3k3?D?N? @>T= t @SA$fjƮ>BB1;5tCC=:A4DsDI<bl&!&mmfm"E|W"s#~"b5UX XYBY X$YUv5YZZZ5!u[X\u\[\U]]5^ӕ]u5_]WU_\5`[U` V^`c`[_6b^6av^W{uc!a`)6cGd_%b`Ov^KcaVeYbUv\ vA"Zu't)LI1lbWee7v:>jCefim$&>0 ;IH9iUJJӶIvIIyPG;XU1ւ'!!r LϴlK L]&5@l7plIq9#Wq'7J_%d0jgnb7Lg ƴfr$hu߶VQ7>kKfwHs7GwD{D~Jbtto6r?=z?WOWH{q7Aw>݄|o{t{y}V}Q|}|w~7@X@BvS!X~#xz-18W JA؂!,.@B/0YX7 0]wh̤qE$sy[cT a,凁x|x׮,cڒWx423C0 8xٸ8x鸎iEzKR+`Bg" ` mhĠ(?*,&wFmzgFJB"8(,?A΢E=gFhF-J&gm:ym\9BFI/?R,V9m`n BhMʑRMWn&l" `Gt*c(َ9yٹ9y鹞9y:z :z!:%z)-1:5z9=A:EzI@LzqaVtAb:;e' =t!t:+xK:)@Q:EN:ŨZzPzgq$ !,1111111111111111111111111111111111111111111111('''&&%%%$#""!!!!!!"""#%&'''''''(((((((*-. / / / . - , + - /24411336665<#(D(/I*2K+3M+5O+5Q+6Q,6Q+7R%8W<^@`C`E`F^=[-R*O(N)N)M'L'K'H(G(G(F(F(E(E(E(E(E *A%+8)-1,//-2//2014527:3;;;C7BI2FM1GN1LT4T^;Xb;WbAT^DOXFKQHIINIFUJDYDA]@?^>>`=d3Eh9Hl5Nr.Sw0Tz0W}+Y+_*i+n0n0o0x26:=?CILNOQRSasxgrfm{lesqUorKlt?jt8ju5oz7w<~;:>BA?<;>@@DSmƮ¥ȏpU@@??????H*\ȰÇ#JHŋ3jȱǏoIɓ(S\ɲ˗0cʜI͛8)82ϟ@ JѣH*]&S0JիXjʵĝ"Ivh]˶۷p ZG}ޅxKÈ+^|`pB<#˳_~R/BǠ!+1ӺS^ͺwu[P䝞9VvУKQAe޻ggB$]<^؇WN`f#çN~̞x Tpz}u 8_~fF]fFH`X!' ]Xw܀4v@)p^`^v `efW&֕9cC)daי%v:)CT[cM)V} f*[Y#pfyM6ğDIchf9"o-I覜v)S^|hh(CU~ƚSE5H᪨AٹTMk&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL`n:x'H Z̠) z ;($! W0a eHZ6! q> b A 4"xA%2t"x)RV#rA"e2r8FjШFưqicF%a E#^~@ B cG? QȂh!YH(!BdA IIR$hēd"GFR %J@<я`<@.QKQ$0_NҖn)LjҚ5́ 3ZH8A A$ 2pR d+p],T_#k!N1 w 8Pa.Uf$Tm\hCQR HF7=<{ H+Z҉Vt )=JѤ2eM cZiR P*TA3 1ǣ4e))>#TzԬIu1!ˡd1S )+좊|S2C2C@JUJMG ;~ c!"E,bKXn6U hiM$9mj#{M@!HV2Y֎vf;[zVY\ \*E :q)= "[XXQ2i4ock^eWDA3_ .}+_KW00Qlaj.yLb{1 ⲾvlD*>ܩdwAAc.u r2 `gׁ3HV*!h8rx̂3LXTYfc3Qv2bQe4|'y -5WǪ{~PNXƤf,SpE)0+T̬Vmq{\PUqER=<)em} \Dא%[k6{ F+e@V]ʬ [h& hlct"v%Yߪ@3bs/=Ng:͟P!n%iDKZ c+pN@]L@ R 0D4[?]"IE+y߻BYQ~ E~졗϶|tIc>on]yLЫ}D{4ywjWl{2PظOfE һtOO; HܭMP ,v0d@1ffgkFdu k W k-@hCB]ȁ(X,cB w0HA"`8h 6:ȃ7;=(@;X88KxF-!xHC,؂(-70`fWd+]r7r Vf08)hzwH=8Xx#~Rg 0ww5MJe'tO@}R3XQ3USthuW"{ T_(2nVnunYUb v&øŨ-،fM Fbc'Vm P}8?(I?HXHOHx\-Θx}chVhevfȁH0n%o6rSR7@ ct,tuuMTvP "-( 0{|zzؒÈ8<ٓ>@ xCiEYGII9Ki5073cyEه~fۅWiMIYف 8dez(hx\3qj֖xp$iquiIp ΑXȆ[xyɅ^{ɀm>q膑ə8*T]HCٗK@Ya8ćHA9i^Yy6hZYɗʩɜ{H iS@O9)x2HiȞ1穞)iy )k੕ɛZi,iIiYلhCaΨlʡ!*EFBQ%ڢZ&OȢ2zf0):Ihrizh X [GJ kIZ\ Z[+gJU1aʔ)Iisb9P)J:|YBoIWJ)ٚyY^ 7dg:䪭*ږK2HҪ8f蒩j^'mA9ZzںܺЭ:Zz*ߚڮ:Z* ;گ{k  Jj ˭$ #[!{)˲/+4k5{z8K @>zGϐ  P zGpz P Э_0 ẮFJ˴N L;K X PI_ݺބj˶Y rK"JЭM۪W۷o4K H{۪S+@k fZ~+Xpڶ˻뭿[b{K+RKV 0 {=˭0Dːi{ pqP ɠ* ~+ p q۽ử  ⛾۾q +ًpۿ L| ˠ܊zq!<lz*%, ߫/ 5|9W˱l**>\AZ{^>@߭} oپ>J^^`,:-*03j7?5;?OA/CEo2JPoSoUoWY(_R_ac_e?gikmG[sϮo_w?y{}uZ?&ڍ x]zxٕ*xc݂* j]//Ej/+/zpY a<3?n@ڎz*'-k2xٟ.ĺ_?_]Ap q DPB >QD-^ĘQF=~RH%M-ʕ:aFhA'\&%VCTݺ PE%HZT4UTU^ŚUV]2sBp1dž 2!<& RWPh`… FXb I|746*4=P o)M[PjIN[ٯƝ6ܽe>[_=RF\JULPаc{ R?!gϰJ%+!CLMk*=JfN9nRϧo?+P Lt= )Z.Cab:&cCnL 4`Q1 DA /L_vqmFwG#";>#J ԒƗz,J-K/jȃ {ƺ#: Pe1ZL+!hyO?4P?L  "W Q*mWMPK/4SM7t$fASQG%TSOE5UUWeUW_5VYgV[ouW_6X݄%XcXeed6_Zkk6[mڶ[i6\d%wڈv-7`U]u7^gzzշ\~_=*$zgjHjxvX[#vbh3ָY;^cyd_ ^%5-ue6fO9[sng{wVZnhhV:6Nꢥꩭ6ihf]zܲZ鴉^[~縻>W @co3h¥ /;%)\ rsWt)o_<94`>5zgH`F1k*aMkJ((B"QMl<P 1N\_|K:JC IJ\.ХT:q֟tyУ=>R7W<88 J{cDW06|Vpr@S[M "!EA) uiZw:ӼJZW>4ET lfWeRkjw6laKGq[6;E;ϳoMkڵ!j[0.n>V[,xmR6pBzBCc!AMpݻVmm]C /$.nNqXw mE~\60YAVnd Ny=h|vgG{վvo;b>ظ"su҈Y#G7yȤ 7!KL@N4mdF{FF }t8-OzꙇHg9pӛ\v@Ns67E/`u^c9u:z~h-~_m%?QnڢרjBEz+ot7CS6W@ΡZY Ⱦ?NkP! J6 ?8b8{l0kr;{Fl8m5b #| ?<+s*"R@AxАd4,A2,y@ ;Ǣ|,庬, ?f@ӷ*, ,A7/{7؃i-2- B!/B.*<+|:]`>H#.jJ*ظ4THtB/DxLD˯4!+1 C)K1b|`960KLŅha(8F\F\T^D130dlsJ3+cD,#Ac2/>zԩT-[`:zkTţCI}&;ȇXG3xܲ~,0 1[4ȌȎȏɐɑ$ɒ4ɓDɔTɕdɖIŒ5 #qIV;:O5[7x; jؽ5 4S,96\5-w+;4Q+-LU!JMC Kl.lZl\mVT>m^klVPNPglږե\ۮpֳ&.%b[g3JfKJ3nOIKNOnN{hk=+hD 4MƼnTʡ5k4ΫʷKoNko4O,NhLwZpجeDOhspNάTp0$Cpn g/O׵O /|aqqqg#47 jL ͮͅr4L#_rlx<*r1DpS~1 o3߹Nss=I]X ?q9K4Oq//?s/968?nML7ϋE;+psHwtEG6LqB_o=Ou㎳VwXYZ[\]^_(*8"SU:.ucfFdv8OvgF@ fSqXr*Itw\qows m]Krx/ y_EZӄ9W :>6za/&q'7GWgw/$Fʤ*;y@s#KyǤ;b!hĚm#'zMy뼁nڤtzzzr {zzy{@:I,z?{*{H{{ I{kq " IΧ Oa &}G п֗w}̿}'}ܯ}}}}~'}7rԏ9}'~wjq8~w|o|} DŽ·z 7GzF7(h „ 2l!ć#Rh"F3rƏ"G I$J)Wlh%̕/c9&NvssID /1 13  i" -$,8"%x")-2C!a*X 2`a^LhhCB- ,21< {3  CNLS6OC"*P9'uyz8!!Nk&x*S[֘ez@)d )^@'Z̴ό=*hF;z+ꪢLj1SZ*ͭJs-jS#G!ē 1{Ȣо Nĩ;/bb>B^ AVXӗ;N>cNr̜(&S~~:WW7sw'l>E~9瞣2F5ۆ =`seO\HNMx1$ ʝ~~9m<{*O>ImOP'!/yزN{U2| #fF 3AL C(6"! SpW&\G c(Ұ syaOv1̃!%j=6xqD#2V"EQZ(1O[  (n8(9ұr|#θl|# )sl5 isR8 a4z$&3Iq=6)Q[bD)|YҲ%.s]򲗾%0)a<&2e2IP)iRsDZ5mƧTR)q;B[9ShO;9N vH&&8i}ڐp3Ѕ7*щJyFک¹TST^)Q: *Y*bgM>ST¢YuR֨U~N^ ػu]Eb\uG<֝ubSNl=ZzvݤeYVͱlZ:UC0n )Hᶹmos[)$oqmnAK ]zJwʤq\7-w]215x 񢗼rSƖ/`kնLlr߶N+Wzڷ}0u,òJL~"`"&,=էVEgj!b吸bu3UW>1,!$`N~2,)SVr-s[β,1_d>3l4ev3,5˹pf h~%} F;҇4-}iMs˝% Azqn kMũ)7E0U1ZbɠE,LWzQ>D2(I&E-PF P{M>gKFrq\H]m&/7m[6%}iݍ`Z4my{.ou>!9ˎx_D4qpg!vsQ^ h9#dpx=+9NE* Y"2͑|s*{>G2mt!$I_4ǻ_":uW_tudh?zC(<׹󠟝6<'x2+~b3j''cH^LwCߣn%xjɡ/F[I)T=39'^7|qu/~]O֓!ow2|滿3d_"F߾ƫo^-c}QΜӋ7=U]24 QYe, + e^),`), %9߹am ޵[>d)i 69B48" `  Y=ߜ)aQ`>_`y5G5l%^}]"D!x!֭^L88(!Oԡ!!Daaa͕f͠b= "!"&jbNb&&F! :".$nb%R%v"+z"'ZbMZ#H` ","1#-_E( r/#b+~+"5f#+5R"5~'0#81^65#6n;0":#;գ<"=+8c?c5?aӨ!AҠi@#??$;._̉)1aii*=\A/8>wPAfU4*^*FX/\IAtK~*^GDxjUCFd*{Rªl*NCP+&.+6>+FN+V#dn+vk^++jk*OPek#`0/#[4l锂,XIt ~ek+kJlX݀f@@#trk䁬JMר~ʍpI#2*3|lϖ,T,tuL״j*{8qWҪjW>֪%lrB*H?4#|#HMєAI͇цV)N.mzl,l@LHP/ D-)"Bګߦ-XXN2i+2h Ώ 趮8κJAծ.望kFppq8Fz&F"G2bIfDEEEs&nA$5$AebocدJ/aA C3,đH/Zg,BA^60CX`0XhpWpVxpVL A4 CI(D s_ kljBP0U0gpqAJC(ڪ ' psA$MJ1WUT0 /0C qBд{o1Y0&7y A\yAn 27Dw!"V#2O$r$/2Vv!ov1$;$sNHr(##+rYr{e Ax(r+C+r,r'۲,xv,'s.s-r%*,kp-ò,5r2s124&s64-36O..(ӔMqD 5s:_>/322[s?3? sA:2+t7S2A3D'Bks83*Dt1HS1?H{;;D'ڐ 1t n>RtXȴGsA3pC4BфAǴHps@,5z4cuVs5TCBďLVEBWp[SF\t][]k10[u5Mvf [@[p_?5c/ctcCL'6vK`viG C,,DSD!~JHO}8xْA|kW MQSSD . ;Č`~7لGW Qg aw?wR65ܪ @ؚ!6W&k,/6@ ;0p~&< JRuKB1$DPj@&Zu$x2nj/aƔ9fM oE  $3B< 0a@%MWfպkW_;lYgю=v=i^][0[ǀ&DбMBXJ\˸,Sf *p| *ƒ=V3+_yB3M´Ss]%eјibL5B) Bt xEfe-ke.#}%l0 ]&T.XR h,EkֵլekfG[tbV-9y^R3|S"|'tWI p=o$-02)M؃8d֨/ˇ͗./bwoySkqD,z)]m|+D#ex a^>X 5F5o}al]v Fhl.2qrc8&rc @H]$yJru)#E򗯼c':2|f)ydvK[&4<Ή4SFr2K?E2;zuj#zȪ1?]PԤn/i-kEχ^Z[׏)5t6-V2mFפIJe#lka܊;䶬nrå'r*}Tuup%qY\`)^miYJ|eŷIq2.Q s#+=-c,oyK@t|KkrM7 8yy?Z بmo*7өtҥ+.m _d#CRG4A1YuyGD{AcY}t3^pu>sNJ,vp{+vy& g/:)t؇sÍ>IȎ(wGl/ x/rHq@wH0 G0&hHfu!j 00x0 iH +p ˦A$(|@p o&0GOIyfkhFva訍Lf'O'GGj_Q-&aQi#+0l7p11q1qAj a q @b]f 0!"$"F1!۱$DDvHvx*f e 2@ĄbL*LzBDt$NDNN4g&*l|0(&YG^"/%SVl2$hr1&(S2@q%^"V#_8R@ qF1Qsk@rr6TGPe`F!D/&cbr&5}7gs88839s9oV3:s:::;s;;;3S>>?;k:s??t@? @# A9 tA A4@!4BsB)?-BS|'!.#;5A3tD߳DMt=Q4EsEY>_@]4FsFi4?ot>k YVGODs>mtH HH:tIIuJTJOJ&~'RHt4.gMV:FǖF6FEӖE6EDvAs:VCBB6BAVnö:VABnI7kWlq=#Wr'wL+;wnrs4r WF=oFs3tvq5rG7tu]w@cWBavKwvu9uwwywkSd~7ddwYAF|$%`7 ha"f! dby fZ.zU${oa{ 1|}-B}Y}W@(B|.~~k!W@w9q z b 8܎,F/h DJ'X5"&)4R0Dc (2R)& ne#5@33:|(؆dxE凛"Vc/)jx \ C9袃7.A؆V*X(_2%-bRYy@ؐ(~G#?Y('y:Yg %yU#eRg89a&- JB.Cf3_C)w"da  Bg,->\YCz~@WR!:ط hY!erCח}gfKYR,ĔyE~17~Qة>~빾>GF쵄ž6'j15tK!վo,|z3gQ[>%)-1?59=A?EIMQ?UY]a?eimq?uy}??"R L&! nſk6{iʿߡ<d <0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[ֻb3Ν<{ 4СD=y4ԩTZ5֭\' UU !i,'''&%#""##$''''((()+-/ / / / - , ,,,,,.14677666666:B!E%I&K*M'J'H'G'G'H'I'H'F(E(E(E(F(F(F(F(F(F(F'E"'@%);*.9,2;.2B-7I,6P)8S$=Y#@a'Ac,Cc7A_@@]DBXDFOCLEDM?GM9GL4JN5LQ5PV7S\9Va*U%j"u!ou$`u'Xv+Xx,Xy-X{+Y~/^3e4k3o5t8y;}=?@DT{nq|j}fucqan]m\n^pfrowt{z}~xjb[S~HtAsAu@y=}:8789;==?@A@???@BDHGECA@@@??@@@@@@@FK\w꽡ᆱõźȿ÷xmcWTROOPQRRRRRTfۯH*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͛8)82ϟ@ JѣH*]gLJJիXjs'AEzٳhӪ]˶RG}܆,{ۿ LٞwykW]t &Vׯɲz bf8ְϠC5sO4=Ɲ#;jS${/Lȓ+@Obc=}7ur_O,n /_Ͼk7GzӚi[WOy߁fj}Po'VhMeYgwI\}rj 9%^pjb|qNb%~@Rv uؚGbQ!BA`s"F%kE(`)&{~^RzY$oLģledej|ɖz`aOtdXgq~F*餔\-|u=W%V*ꨤ:ԡkŹfZd9iꬴjkEZR'{k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o Ht> !@ FkP@ Jǁ H di( y! 2CN(, s<$oB?!Hp)DE)ˉJ bMĩ<+Ha&qPU< ר3"6Rr;ʑ{d#RjэtO)򑑌<!I&RRY qV !R#x@>$$GKv񐘴dQL G2"%%iYI:fTF'f:Ќ3uK-di6IKZ' '4oD9 ǟ|s1IC?~RԣIM|K 8t )5Jo mh bp"T u(D3:QTtF1PR )G7ZҎHE4$PnӜt =鱂R֔-è{ְQxKYSEaUU]Sj1ZJX4$?oBU5] &WVziķΒeU$K.r`_zؽӫ,l-JVrl%f WI: lad"# s ZG8 +;˯:1'fc V-oV19mq}Ds˭4n-źnn[Ż xKF Mzӛ|K~7 |_>|, C01!zYSxqzIlbX*~c x/qb=i<"F0DFL>2z`f"_@XO7 .RzB$e.{Y˴-839PG^77f*⫄u7Mo{aȴ+o P8mc%)8ǫ STA ȕ0q7`&ޛ;G=>\Ip'Z^_C `V0k[HV9L;7q~;IHMo  :O r7w&>HW{וÝ@|G{鍼[y/=忀z~ꭽ?+^|^S|ыu_xkIO^۷~ѷ.dW|;_N4Փ?i7_۟{OPfW ^,'qGipΗrGjkq^~zHh鵀gbzg}ڇ^48_x!( }^7_I vuew@wRքe s'l@۰fK^k8igjXs؆Džkth|rtȆnxlxh|wjT rP=Xyh-f|*Qe~׊]؂Xٗ^ȋ؋'|Ř^~pqɈ^X(qḧ́@0E؉GXȌ؈5h(؏isG`7،ya@ 얅}yU(}u֑u{6u^"yg_, _/Iz%Χ7^9<)H|>89Uy75IGpf5y_9hǔZƑr! fwwU^SnwVoo (rI q}pOY|q9gS z֗wu^0&| zP| x[&t遖9{ dSuW8~ə^T9i^%9[i_9)bdqs^xٜiIq 0 i bF JyВyQncxgdY qbLI70ηP؟ZlvU f j z)L*jz i!zjZ) hf ^({3Z_y.9yaE`Ԟ8*G" F_1cE_WcY] _`aʥcJ%gjk`iצpJcU:_r: v|Jd`~F^j5x !Jj|zʩcʨl*q*JjyŪ)檭 51-BIǐUZ<U-CSp0eO%Y+ʺ@ɺTCZ2P%5GM+ $HSF:SR-uSҮ ǥpFJ/jWRO{+P)AQܚ"\˳";$[&{(..:+@i ).M @ [0q{0yA0Q{7.-\;X@ ^еINwжlSpcA Di X0 0iqi,EIPPFZ6U;[ڂR)9 z+X0^kA;Q-TQ8%TZ!ZF%K"x; @|L+D@00 t0`kB0 kw[ q{!j۸r@ 5P/Yky [k0i,I  \R{Kwkд;+K1\6|8L?{V>@l<D\FLC|JALPQ`<];Lhi=,lnirLt\`|xzYo @AZ}SȎ ŐL<ɔĖ|FɚLĜ@ɠܹ1@pƢʜʰ,ijǵŲ|˽˺lDǿ\ɥ[Lż|Ӭ ʂ\BA[\< < ìB,@ lFB‹@#)Pqd44uS}}-M85Z Q ]T @m!5$}M"' /R&]--9m.3-Q:-D;}#] J2@K<}J6B TId]H7ZmYX5fU}m]= B=v-sqMׄ=a ΘؕKΝ,ٚLٗlّٔuTK ٞLGًڀ~ڌlڛ ],۟ dSK M۵ۡ ?xLܝ-amܿJW-VLݚkՍݾݷ!qGmrށ= p,=[\ߝ m-vԝጳ@+p {/ 4  !G˿iPSaH+1~W @ `” c` 0 >^0{ hU/˳1e{h+kp9 4^]qKYiqYྡྷRiWc  )i$d4FNpdy{>G G 2!1UẮm봚9;ܲN>aꨎ  * LPQ1M ?_Fߒi FL E;a|@X4V[O.X@H x~h:?\5.,~Rkc.W?X XӒDE/T D8T$  AIC=~RH%MDRJ-]5mYSΗ6$H3PDTR*i&TTUc>XSZ<&EUuśWoߨ-E_,xR‰⃒ [Xf5WPZhnjٰc '78Zlڵm-A3:hnD0͝,md¸5Ͻ^ݼu/*\}e>k#Udv=r+.D0ACʼ:Ϸ\B /0C 7C0DG$DOD1EWdE_1FgFo1GwG2H!T,#2I%4%R8Fr;HP,-P#H1Eہg@Jh'(]^l!8IL1hlQtc1 # øc7hi( 8># Aњ*<Ù8ȂV+6QxKDH$u SXBAg4( HTC =DvbwaDΉP<rTj9ȩDd"HF6ґd$%9IJVҒd&5INvғe(E٢r(Ôy%KFҕ.IH"$!'bޤgB%V@E,) uc6SJP3M7QDg@⩉0Yd{DHMlV z6WFgr=?g:ABGe;oYM{~<P}s"PvE'f"D b&ё" J3B& 0 ԦUg> ́ B)OTvͅ.]N}.+`6o^ٚU$q I4{o$iHE*@Y%";2sL.faaA ' ?6^ a - gxG !- qSx%d " b{5bшN9 1oĚ" ,Ŋ#WpÈ}1eΙ΢=ZV(!U83=dŒ̐Msb׬h5šB ?^Da@ÓAhk\3!P$6t[> uwX+ձugaĮج)h  B k!?{S񔇽e_~8O{ߞ}/yG_~|o{oĊ;IWi: |k<^aY^aG냄>;JcT@?&xL> , `K,I@;`;"}۹*9µvc,#\-Apc5GT8?: 0x{N#*@βNJBi3s5929rU/|B(5j8A:3;7W3Bx{C"D$<:+>C',7:5P?U{,WjBh+D7 XI`1b2CK[2ʂE|I+p=([=EV Fв9?LN#[,>x {E_<?ed@$6{ -N@1ZR3m o7Q\.a46|6}G~6GҮ'*H}lEaǀH{HHHZȍoHn-kHHI,IU,ɏ,6dȓ$dI9 .LID;I:Iɞ2"ʢ4 TʺHʥtI0I i[z$]*z(tʕʯ'D|)(U)0KBZ%*j* r+˼) mb')T) 詵$䩶$ͤ+RL)%tL M ̨̀ĀH8r!<(|LLM<(LL QN$l+xQJ(d)"r)N"(Nt TOð $ΕOp;*DBĄDO%&d-СB&P%ĀIZZ+MJݴ% ${K %0ѴB lک *@+)l}N\ѵe5LMv"K"eQB+ Q%R*+,-./01%253U8(!}*:RE:$(StKťR;()z'EF]HETҪ?=uTY;TxI̻ONIUSI5 =V@q+Z`U$QdH5UcfPahijklmn pq%r5sE tUuev WwxWzWuE{}W~x ؁5u-؃UXUqM؅|؈t؋׉X؍؎Xِ5ؑ%Y5ٓWXRٍMٙךY{ٜםYي٠mס%Z=Z-%&6}ѤYZ-YZEYZWZ ۞]Z)[]ۛm{۷W׸ۨ[x[[eY=%cUۼe׾}[m[][[ [[[=׿(-rH-U@ o]=(5= ݪB]]ם4Pݚڥ]]^}]וS5Ouյ] ޕ^ _5^%_5_E_^_E}_]_}v `=^  `\^O6 &a _`tZ.\FZFWZ!Z"Z#Z$YmZ(c( b)\*6W%Z,NZ->Z.&Z/Z0>Y06}YXmY1Y5Y6Y7Y8VY9c+.W:&YC@6~DjxFqvjj  QƁ3-'8  F3l 6Tj>!2!!rgRJm֠@!ʈ B!Bn@mdFQr惐#:"4Z";*r6n 5)Jwnf){#ox g o i fVV'VP!2UNLqJ(c mV%4`` Nbqbq%[)V}b=,01'273G4W5g6w78(O+;NltA'UcswSC/PDOElGtJt4MPQ'R7SGTWUgVϦFwXǚRRX+4 SY'tQGP8u+]@ L-uOv^>M"P1bL֌P a*&=vtDvjDQĄu]hv:S v$ Hǝw/ !'TUZvzNgr(O v80߅x?xN )^)A*'xLH6Pu%_ԩ:$ w!q UTlyOy˝on}4%kS|g$fӾzEt'GWgw,YX@{"GpTG@KH \8wuxEu ǏHxx xP۸\'w|ȗ|?۠;w4}|}oܯ H@]hgo}7~/G2KW}Yx4HuxHI Ip4?'KhDui B40ͣul!Ĉ'Nui%Z\Â1h$ʉda c,g]D!ȁ"ӐLgJY/9z$*sM$hgIR64(LIo2 'հbǒ-k,ڴjײ5ycUSQ۪u$IIHю'ThdIuJC\ʃte*C9CPiHFXnǭw&kQ@ڥmƽZ7Xʽ3o9d5J(Ee<[-P?~d{s;Jfy=Z"%_(-i֑j͢^C*ȠN,!^x {#q蠇 "1"5x#zmH:_CM8%C!@E8I\-8UV7#9ǟ:[%NByTje,W6e"_s$Y#\%,Z#`N1gNZgw.Zf9dZt9h(#*2mV%祧^ agmMI,6W{w#V:d߳>H~Ch/ƬA@*CvA.j۬:d <0|Yͫ`yT$Ї,u^".Ua{PCt】ZW,\1{d+2VR7oPˋB€{DjK%|VK$eLz Q⋋yFy ]-8B3Yc;bw-%wn<*қDS{=;3>>髿>>???D?'< @%| #(A.p~ 3 rV421$aX.|! SBư>!ؾaRC!2N|"=HDg$-r,)b p1#):+89ұI7Gj|8r<)xE8P|$$#)IшHcy8MrrP!'SURll%,c)Yꕴ%.sr%0I^ <,e2@Rx&4)iNS5Pބ6dHCSuS*Ϝ*JSpR-\͆ܡA`vb!mҠ}hC0S` Us N(АBkd Xҕ:T ^= Ni ?, XɠF=QԥUL}:UR DL NX0:B´{F ܪGՐЫ+]ָu ^Zȼ ,bj\Zl1)EqB *]Si$LnӥȪ4̃BiXY+bPu+fJԱ^hLeY᫶O&Q2T*SSEY]\i W= lҷhoL$g,T393~0R S3 01 ".1A|X+n1'x)Ips,c31!GFN㑓$xLb3X2A YAK 0xd4@-jcz@j?Eln hFD #CA:č1cI8ҔQI,ͯS5 /⨟}@H=?gZӭ^3J,iTEw*uq}k]{ҼF7N?` c`*(RUةYl7O( 1PeLΰ`u=/rzc(lF[%7X[Q{-7W ~O6_ Ex=>g9N؃o\+@r[< /i5.^5n =isIʼn."|?_zKg]OxUt!/Qvg_w 9]Ԥxѱ8~h:׷umڇ~x+>go#g/OvO^kU)`XZ6UjXz`~ m ``` J Ơ٠   ! "1!9aN!V)`(\!+ C)q~B( @x)l8DBDOBDWX8́ԁP@>KVdC,B$B$Y`A>>PB."K6KUCXdFҁ3dU6 Cd%!:#B$RDRdKm9%tuP.,A4%*!N %`v9P%P.tZQ"1d5P<@FPV>H3~QVefTJ`Nb*f߭ZgB_QhB݁±i~Q ª $_fJeeCpҖmAeXEE7ndGNAԁ7:At;^gDoe1&g]$)OgCԖNeXn{*cQwVsT#*XЀmQg4Jg "Ёmdh$R%)E's9Wnc>+C-j)g@g sؤP{(Dai؂-)x+bCP=!cbHh j% b 4&2WHQyіzuę,pCfi:A&+(Ob ΁6)橞))  **Du2- L @4SšNjJ$hq*JajU:njnOfRjAJFDua9Aj||[&Sۛ1[jheS *.sjYCaM]za2+ƨ6XvZCA$lZ|+>@o Ė+E(aI~jc ,šD&,66,FN,fЛ>2XA`DxiP?9D Vl>@j.a$b%Y!j6b""&=@#, +/eDx:sY ܁zlb9>"~X0@'߆%AJm/n>D2YBe¨9jRyJJV$&#FjdxGU:BP9jI:>{%#ZB`:RM@@!C!NUWnB"( ]P"Akf & l`.%`>`&i/nVcdҀdeځepk&qc""MB<&.D|FzRC.fpp 'q:fKNxRߊtFcujHɰi:b/A-x/B"{>evojmͧ(q0C/'2~gz4F"@((&AjH6DA$#ˎ@(.gi_@0'ЖŐA.i*i g2k])"֩&(2))2**2+7rl^(,RSICZkRŦ.l- +L+2D(.w&3 %@ --2-Eէ*77/as8wq0*l387O8w32E52+6cOsj+0I]b3?6eWe_6fgfov[g6hv6ii6jj6kh6lǶlvl6m6nvk۶no6opnq7j7r/q+7s?o;7tOnjK7ug7lcvwwjsw7ix7y7yBzg7zvǷ|S7}ws14g+čhw}߷/7q8/pvB*Tg++'B*v$<<4:Goyg;;܃6v;h[yOlK{vu7y{??'?@`*=yru`*F҇ <EԸcGAIdI'QeK/ 4`X; $IT'V8E ~.T1MSO9vA%JjriW0~;dXgnM6Yok׭9J1]ܻG#R`'VVq\9E gG%}w X0a P qǨ-1k~[6kڵK DouN\8\ǢF>NVtխ{Ş=.۽Oo|kǿo-?:k*z.)*\A4 ) 1P 9A QI,QLQYlaQiqQy R (r#qe*RJ#ӦJ$/ SLJ3ܲ,,J6D)8:3O8qrXx$\JԌTs7G!TI)JE4NCOt=GOKMJ5U=()T MEPS__ W^UQ4VOQoՕ Q [;vE@1e6*MeZk-sэoW6juwF4UoWnӔ7XIJᶩFajIv8~a%9AXEx1dW~bMe_f#+iuu}^NZܗ?eW}˴Vg͗S5- xIQYuبnvq7e7u-w^:DdU㎊ d)d8ķ)rȽV;%߱5t5|K7Yoa/Uq]y߁^/O^o硏^驯\RzW8rf^XD]G FeQFA)Hb <h=b?V1$̀ t TǬOG^!4 cS4 /h/*N`+T)),0 WM:B*NC eM8x?)0(`=%0pE T]'5HXi a KbTA4hG!`/Be $dGA2i:nfȖ ɌF$HICЖX$ BN(`CRI#)Q10j hDUPSB'6%(iQ`41}iHZa XSn0 $ I2ʉLuQn"g.`SHWh*<#T *Z dAPBb uC!QNE1QnvGAR%5NRkKaSN5iJizSԝ9OySB%Q[jT.5RUBUUuUԬnTjUըUK%kYzVBhDPJecqZ+Wzנ|j^jSV%,L {ؤ*bA9PX|ve,R{Yj֬Ŭg Ɗv%mSO[T ,$ZZvmn;umLmBTys ҵu{]B7.uv-h/T0)MVPX`M aO!7)~[J,mJJI&< 60 «Rv# WH0CaxkWnq9,wع0.OL%n1mLc,!_dKUV򐭜yreg˫Bf-kFq1_=1e89g泜+$Lɶֵ祭ehW^WJfS"#= jΊZ/ HZ녘ٸʕe5I]}\vׁ_`'6^W;bkm6Z]hu_v_l4٨ݶpt]-7}tt>Sn{_=oz;ƪ1ms&x p\RmXQO"CqidxFJD"Lsea a gT?m(`!)_e [|" hns$E?=]ML͇]ԉ~Ko?Q=~qit'y A'̍=&z|٠ؒ3̓McFԘ))l""h ߔU\, -]z Aѧyw QKsj|J2ڟR?>#< SOQA:"m1p)xyQ/>4qS`KƤ~ UD?q/"G])J/W))Ipă/ 0*)I&Ј,3PGBD0*Qfhf  5"*pTءD@PN0z0In^mi0DHAPp " h )K0GnpB4@0Ng2H<@J)T26LHj4I!J~h1) dH6pJ$W#qiAH7qJ:ANՈZEQJEqJqQGHJ @m"./*;0 *Q]"QC!I)1NC!w4D#"3p e!O&*Pɛb/ !D`4}U%_H&&!)|2bxi(g&~ ]O){r~R(r'02*B1WR. 0-2&UrLoiNC *  t.xrV2N11 i"'..3162i>3I4-SL3 P1USH<`63)-} tNr~F.8 (7R3?EGpXL h (H F? 4?AtZgtF@%=t%\ԯjeB==3Cs :J:CUt< R`flgiC&BWFjeD)JG4G&ujzƩItJ1 FGJtKwK4LtLɴLL4MtMٴMM+n;+F qN ).bh(BO)-\!htQ5z6)pHO*mRVRZuR^uS ɍ2ȎH:RTKɥEeRUFɔ^XUV e5M?QD<@)SFZŵxі#,uMi\zu']O+aTW(~*%^zv4c"95PvaFa!6b%vb)b-b16c5vc9VF\>\BZFcϥ>!Eu%k[aet^&F\?'KEvgCgevf?J^rHiqvHhsIm&.jeWtp4Ck&pGOlG^gsl4A6Wlik6I6j=Vo`PSVmwhhwpwAnTK-pLeo.dž(pGHWLwiit uwauKuad7r[mEGfneuHtFyEvmwyuy]7ebYzczWzYy|WF YńxmƖnppwEJC~7h~Wsh~7hqs7DoevυnϤnvkՖ?r/8ln3p~18oq7~ \ƁsKU Ws*Ti]X]lԆQ8EǂwszDTf7wu7rtKboxX/8}d8x8n#BPBAD čDÎBΘ#x7X-@8"CAY%B{#Eb9>*<.$A {v'B X! }n:K="'#"!qC#!t"B `!*C'XY 6<H( #9*n&f"wB Z# ;dbZ!ګ+!3ZJ!<:"zL!Ԛ"㬱ڨ:"8ڱ (l8;"2MCB&#R{[Ś4`9pZ%a b " #07b5B" z##]Akº#¼{ !L;绻! V97Bۿ:໯?{;5©2 >:j¶ f-.Y>FBO'[-DbGB;BK[# +s(2‘#y 7"7?Aٹ!y#?c̳`+ˉͱ ù\#<"d|\#\ !'Yϭ<5o;B]\U#C" &#y{O0 L>i~q/ 2 "*R=3ā T uܟ>ߵs^Ⳟ}#z]#^>]3֟[~:֡[K"[]՛h# !7 P] [ "9bmܖ K7[)@_#D߻%N_/(.acCW1! Yx`SSVU"[p*Ĝx :tcit#=wV HX5Tu} o愛ٵo޵[`WhHM?=cܹNܺۍ D'~}iߪD-$mfp~uwE mEKcv9xXZ)8Q#È@T8c` FT؎>'r(g*Ώ0g-8+^hdQ)Xj D\Feftf. QjɦFW Enҙex?F~Zj&E*)wy&huz &&cjjXԪ>jih:)E  l> mNKm^mnm~ nKn枋nn oKoދo p*C i<",? qOLqo kZQr\LDHL% s2Ls6[ p:B,tJ/tNS.h9M2+3Gk vbMvLu iWm,P2_Ǎwz)|CM|߂^8'~8}p9F=yoy6cϋ%\鮿{?;:XL:г;Y9SN|?mEm;OO}Wo}~O~柏~~O=)X?# h*p^-v1Q 3 ԂQC;XOp,l _p4 op< qD,$*qLl(JqT,jq\0qd,ψ4qll8qta $t̤)O)6dcb(24D-o4ĠeRrĴ2]Ze1 M3$ ,/lj`%{iI`~r,YKfʲ4;ߩ.b@'1Έ!~,''''%#"$%''(())))34543238"?%D&G'L'L'K'I'F'F'D'C'A'C'D'F'F(F(F(F(F(G!+J%-H(.G+-G+.C-/:./8/2404=/5C.6J-6N,6P,6R.7U59];;c;;c;;c;;c;;c<;98:<=?@ACGECBBAB@???@@@@@@CHP\i޶Ⱄ尝괦ɻļsfZSRRRQPONLGA=<;<<|;v7o6n5i2a/Z,[z-W|H*\ȰÇ#JHŋ3jȱǏ  Iɓ(S\ɲ˗0cʜI͛8Gɳϟ@ JѣHV)S@JիXjʵΦF<@Vԃg]˶۷pT'bڅNݎtwJ-KpÈ+^̸MɞTX?>[_u%g&tt6H9rvL۸s+%<2-쨛17a~-~V{Fg|wyuO ;{;C\{tӖ.l:q}E\d9w\5FtQpmFhQIeaaad 67_g'b _ʖ<ؚueޱ6% шax^QXf?vU&IU\pIx@(WKwN矀cy$2u痠6裐NpVj饘fZj駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/Z+o/O?_HpL:}| Zep CHB(\ S(|! I(6 s@d0C\8$dP&RQ PbD,FqQH2+fLѨ6q+n#.N Z$ь>6cأH>\! 9H@*# 9IGH%)IK62t(PFoQ4t C `*gIZ̥.&) 0Fbϸ~γ.QI=78 B=ZτR(63Qjr(H9RvRHiJ-D \ NR3#"-0E9Fpta(s*9*աVT/8.Pb & cl& AV2A 8؏ujW$ K%^7:Xo֣^OƒԱS%ZB$a1P"~~pbK2`*dZ֖MlnNQpY.I@-k[ZCG<䑏tRm_{{V<ϫ^1 /ֻ w o!Tv'۫ ${_ޯ~_~R/}o! H ^/` vq',X gF10b { C\Ƌ0& c"87l[dN4αr EAUP''9+7w>u}~}g`'Wj1u~Y$X%5ֶ{f&~~6n~p|{x ('XWh]w (-ͥ4Ox ~:x7&lj^a9;؃~n`{|UuMB(Յ'U p7-UF3YV\sTmzVgJTpSxfUY3E8gȉֵ<(VDT { T0}'acIዄ(S`a%F` 1EHgv&FXsv# jPXe7(xQxACKss #Yp)a 6}t v ZS*-ep4Cqi~VzQ)núÚg1Zxo;r;t[v{xvѲ0+ ~'];+Wd²j[*bd\Uk :˹rkyt-, *rwԪkfFۻ Aqp j>{@p ~ 0s >;`;@C +p`>l#|>0\ӫ>S *e \>(,<4.ptq\>0   3ƛsaP>g l?`0 4`>. 5i,K?p3vȎ3u>x\>zlyk?Z`S@  JLv>ŬYƤ>0 3 +ʙ\>+6Sd.dȇ+ʢp?>ڜ,?|+ \s ߼Χ|>|0kM?ll>=\>9j l溫>Z 3P>3M5MC a% qҢCL2l=)-lH\: D}>G:` d :qnm !ovm>{=z׀=ׂ- ׆M|~ w؅}]>}؃ك}ٌٓ ˬRq aEH}ٚسٝ؉۷~۸]۔Mۿ-پ>ܙ]̭m ~uM]ٲ}-ܘ ݖm]m>ڭmHMMޠ.]>RmϚ }-?Ý_;>ˍM, c~>17?AnB]>L+NN\ϩSۼW+k><#k?@ |P< qD.sζZ聼J塜Ή>Cmʮ+ ɵ>ܼ.`rN#R`sǾt7qێ>~p@ŦP,\~@LA3Bg Ӯ'ԾP??5A ?!#%'?2-3o>9?o /:_J-w*ߐ2`KʳV }gUqji?j]Ϻ1Ijn`KyGI{#TtPpk[rڢ뫎oQ0nPҶ):FtPtG?Oq3WLsQOONoR2Wkm6&W'^s1Pi/s"gE?'pk ?ߏlM\ƕ@@ DPB <Q0@? (lk6 E, R$I#t an{I%N)ss3'G+,DPiŒ'whxѻsϹE4K)s*n 7?|j/cl`B?6yz#r?ձ+qOD7KtDp{,(>vpYK$ +,P"Q ̈́ ) F‚+0"=_B>VZA&6WCDHbHDVȁ#a]d[_Հ!J狍:jȮV,sǜ79ssw=t|EG:#.=O/ӡ>uK}⡐C'!X}X:קu}fGӾ{Owyixnq 5\|r)'xwMx퇐{u~rj!C(>oЏ~ܟ?s]9ɝЉπr(zP8av/ 8RQ'pۇۇ}&|C@8۸ Ľ D89!RxNH@@T#!0C\PA A8.N3L8&@LAB C1C#9*tRBK8K-.,c!/8/0<¬ #9A}pB(A ACD/D \|D3DEDDG'Dĉ ʼnF,[!X˸WXDAY2,F#bDԋĒÌEõCD>@:WTBk|@` ?n o[X4Gr\ǿD\AJ@C Gg$GGZk8~QGQĽ? 'pƆ|HX HT\n d9cTIǏs[F,Gh^[Dt#( A?XBy彿l.xTot*]ж1-!8 =]7M9}DR1m;}=k!tS8T+B5S4UPTT8K5V\H{S!;; 05);th<ҿ YAU#y4uAZmUG(kTа?R> ,cTtGgVemibUchFtn}YVVr5ԚW{W[GX=SR[͸amJ@Rx8RW9LW0lؔ{،XBX8بXX ّSم=YM٪[YmYc؆|{ER}YYMYY ZZ,Z-ڛmɜu٣UL}ZKZZZ=ZMڟ}گ[/[СU[jk:[z8ۼ=UBiQS} \\M0s-# =\" @\7ɡ]Ԫ[ūE"Uuׅؕ٥ڵub^])Ř-E^wŴ\X5?P^P퍑]^ވPn李}^?@_=__m__߉`_UߥϠZ]Ur8v()s`q__F5U+a=a^M`ݖe]a]^ saa!aEbVbnߥaYh蝅Z`}r`r a5VpXu$b7Y"6b)4%b6fbc=aAc;.dm)` c@cb^..](j20`<&AfEN FeMdB`5n!`F;~WVneGVYfRebef֗bZIb?s coSeQc?>dVVvSv&wv爀qxޖz{g}7~|vg&烞gmAn臀h|vhg^苎hl~hxfi U UbXM>m^0vjӗghtIe[H>b4d=r^jCdc.d6_bnj= _ad^ߝd~rcdP6c^~dq8ֻU㭞i$Ffj9cjXcVĦaq6bsfGjlʆlÖl╞1MikP~lflsQ5kceqCeʞFh߮m]vfe챡UhY xn?*r0Xn٦޾4NF>kJ.~*^oG6gw[q0R 7)sa~°̈ӥpβөQ01A*4+Dh!q \oq\" r/r0ϰh_rZ+\ x%!%I+.X.Gq7b¥5* .jr؄$mᦛFηw -lA?\X]t[sT]()c|zmjEa-q[] 2J"7tA lRHU=SlU@QQ0G6 ڡݠEķX峕Q ׵c7]6fO,:ܸ{ >cy7f[VyѮxinW$b! /:8&jqQ@E?{$ֶt}|(\ux2 AIx,>yAo<҃i{+30}q9 Ĵĥ)rR8 vO5Q)T mlc UR Rm3hXt1A&n1Wx31A8>p,dyFNp$yFNr' 'T,kYEl[B fy,nS 80;щ) 0# |Stb;Ax,Q3!}9Kvg:1 -0HE+::ݱ5{D€֑)kz5-3))`&0 D)'?l X3pDl;bD lc{ x lw#uc}.6x p+|n<Q(*tl߃ f=mIC<6yMm ;x ~#s[_gTӡ.yđp/GcB+pĔ7r 00+tP:a@V/ u#`0@D?NAΙY xA|xG w\^?<5/yK??}[/ا=k׽a&u/|=//~O/w>}<+tLu[8h۽.s_[-^!i_1__ 6V`E~^]q  _}ǹC%y#c8#c96X,>"1X7c=#c>c?#7#@c!)$ @#C֝CF $EDn$FrAz$<H$II$[ŊJ}RyA7d K$?W@ Ov QBM^@ %UL@RS$.ClUX^&'@%JR%\N^$P6PXܐDA\edqeePdEA|b,RU.dN&eVe^&fffn&gvSՈh&ihij&hk&lZLl&m&j&nfK&om&po'qp'reFfr6m:'tftNg'u:u^grfv'wvgp'gxgtzD.xD\.x70yjwgn'}֦}l~&k' DLLɘ Y%HW z tĨD@ŀtXhsV("(qVhc6T-XX HLL!(a.@zMDqA fBgZgjiv}sEs)閪Gph^ސ:Iכii)Ή} s )!-Pz(Z)D@Aw`ۨ&XDQAQRW*A꩒D@jnѨXѦnCj**~j*j6k*"N&^kbj.+R볎J+z+n몶kkv &Z+Zkkk.,2,,F5j݋"Q)"EŪk*&쿞%ˮ%,.,GbxW)kN-m"*-2-"X֗+Q>L miGzʛJ8-zmЪ-mjNL|EMtEN&*Y)F?ĭVgGx%J.{mtJRnZbnuj.sVgmV9G4GO4X-FlRgggoyڊ뾮ooS!o*l2/|:o:/B (~f2'~uv f/Ngg/z&֯/x@T/DzARwA 0oWzTT0#Gdp_0 ^K7|0o0  K QI 0[&Q.@Adxi1'/17?1GO1W}镏`qh1peG)M%)pUA<(Ap;G6[`AR4sKGXM(LJsMGmLÏOP3N[NODOA/KVd]Fh4 A0)HW4qMoP :C4Dmpt[K2\!YCWK^F_GpW`OtAv'#v\2`׋`IdRLBVB-Z+D2gˬgYG.@g}l Rs)?eW vYlqE6*$e-?uXp!w,o;|ll:p7RYwbw|LDNlqV )W]ʾzZr̆| Xr0zZB{xy@phY*r`'3YAL=`3tQ*Ax<G?qɆ#DGQgxĉs/ui<5APxTURii|$Hy+uxQO,d 7󐠎;98f'_=G?~? M.MV_=dy>Y@Gsy|.sD웻˶]IsN7~8ix~]>>(gCR^>L^?=~Rasg?>19j;:$vSe `>HA?@8`A&\`B h0bEfԸ#ʼn ? !C?1vT#J'G|I̘,q9"ɔ49hQG ժU"VI10 =œARWD[Y*m])lk6 y_ lQ{>,@ʊ7vL2Ηs,桗Qzgn z̰q-q% ]*%LԺ+# qj۝Y'M=0`7Q(o׊oҡKӁ/^t2ϛ:Ga돣P*n2 ㎳H+rO?#!(EB-w/Oq= ̑- P@F4ă2+s 6B 7%gL,eO{l˂N?t9siS ?sH$u2ND+7qg^zǝg__~{~~_}WEÖ[΀q:9N:1ݠpDn`V<ܡx tbZ`8@2Ё Yc\? p4 G=0K [B13D@J'\B!k)g0%! U N{+:Gܡ YʉS\E6q0pD*7~qM>! 80N p,>{0:X9?d"Qxԑ}+Y/ ^`\0aS#<8Qȥqq.?AW2Ye.m\07L&S[A,gY[e㰹m>, :qٲrҹN&(0Ays'0ILvsߔe i9PiQ>8{Qt?*D󗎘&EtfT,u-R o`';1ut&D8TtiKhJT2N P`԰N2s&{0a?ϑ^xV\VՅ՝z6jW*VE\]Wp׵å 5U+a ֑fe)9FqUagX:.},77OƽA L_8Ĵw;>.MJ׸>D!PӓZ}<dr֎2+1״(#|ֺծqV%/rɥ׹e:KIvͮH)վmx[ˬz C&ud"Of8n;n}Q`t P\nrϜB:Hk:P2u'SHLe'gQޱ|38!xe(Ki2Ѭf-=vc<̪ܸ?rrC)gǝЀ>+Mh8WZ&~L@3N]n\C+ϓ_a8}(͛^3}W7ӟu \9"n c8+e*90Xos/9}nWgݿ܊hF뙀ˎ0R%w^; uW8{7X3΢v=J]RSUN9q;`܋7.x^&Zs%'Ӳ#g8&وOXdt+YE>ځ|sgks97.7R =1ft9 $cB:4+(h? +^op މ7wg<8||e_KU(xrrgn'~'/=ɿFތ>NYYMr a[єcyiٖy{a_k>yyq`*j % ! ,111111111111111111111122222222225H6p:ADEHLNOOQPQRRS\؅մĺԷñ}wm|cu`q`qcqknd_ZSNE@@@@@??>>>>@@ABACC@=::;>99~:z?wDsIoyNjt@is8ir:ip=hn?cfA`bB]aAY`!@&D'E'F'H'I'K'L'M'M'M'M'K'H'F'F'F'F'F(F(F(F(F&)F,-F-0F.2F.5H.6K-6O,5P*6R'8V$;\">`!@d"Dg&Hk&Nr)Vx*[}*\+`-f0l3o4o5m7i9f9b;^=W}@NtBJoCGkFFgKGdQF^SG]TI\QG]ME_GD`BB`??_??_>>`==c<&~ $)Bz $"E22|#)I2dE р$ P)Qb)B+kY􀕳\!z_ЖB(f:Ќ4IjZB"| sf(4a!B laQ=,TH:iv Ap#С x(" ѦA P%,9B6L8H@olXV!2+,l[C b$6a=0B {iBSiT} Tx@@?ć(XbQ0PdJS& iH`6@ 8^hKeT58|!_U c 6u!MlAr-H >ġE)VF4 P4 *},A"Ȳ.6씶/* o5!†$IU+dq6%m`F8В%kl8Bt*A , ЭUzIOe%b: *a`puͺX.ׇ Шa: `dd'. _ج!y}HP:xt`hJ\8p d!A&Ȑld"'9( Y! (~+DV+`g-HGUYB Psf ZojP^%e|V'eH "V`;2\n]} Vu lQ߬_PwmS]>\5y>Ѐ>A*9tYeZCwA@kB@* 7 38| 7MH@\2ůi6 )!@!u 5(Aڽm-%DX VȕY^f8T'N[l'X{ѩNv '1~1?qfwpԥ6T PY1e1eR{ў5%p0gb}N11fc>qxa 7ϡkt3a@+dӫef{9Gt07{G_707=`lFiN u nG?dQ90N@ N\)>٧)?qB._؄@Y$ FʆUk_v/s7לT:sQUU ͅ}ksrhV}` _pvdYܥ[t u{3u 0tOEwʵuX7Q(6P7uU6xuyusH4]S7EǕ_d| `؅878hd(~bJD L LQFDTHDWv- ea׷rHl`va0vvƵ?tv@ s7b7(KcAEdGMdcC;D@ hh&B8( lVw(`2ȇU95LJaiiUHlwlx!B\0jOWsXwl{wɸoxfh_5Ogpsp7pH{ަy /W| fx|!GyvgFW6\$7w%Iz(*,ْ.K2 Q02=q @5i]3V=93XY%Fi1?4iJInM!C)!VI14DJ K/'VdCXY&sanjxz|ٗ~ 09y٘i9YIy ٙY陧9ᘵy9ٛɘyYȩ ǙĹ ǹry9i B0;ɝٹ \@ \` Ti@հ ̉ y_h [h@ɠ9`RpOU*yʰ  d  h  Y `靃)ߩ BI0JQ opXyܠ p/P:ln[ i_h ]9*dz) jW`{:~ꥈ)@@FZtWddjyޠ pZH*MJr:iX0^ x i  qګ)ژt h FZyɚ ϊ:皮p䠢 :Ȋ*j<]xO۝ ژh@JzI/Zi&(W ^ʰj ˬ9ʮ4kK=Հ j: /ۯP˝ L: d;N) 4@d4 hp4@krI1qlux}[k 뷈k˸˷˷k۠T0N\@$f\@{I Zʀ; K+˺{{+Kۺ {ۻ뼍Kn۶?n ӛ븫۽k{ꮂ)4;Q<;;kB{ ľ j | A\p + b +˲Rz3ۘG+p$[RÝ,鿞K13lèK J P@wjGlj? ńY+J"̤K1Û*,X@ R ڬYJmip*uzۊ4+CsLÇyȃň)W<WʞP ˵!?IsJ˵,+Wʘ˾`ॺGj˕,)$k`>:z @ * d\7iU@ L ƌ= -)R̽Ь* -+#M+&=+M *Jj )-,m8A-CEͦG=IN7[ʤ6k#ԥ;0 P HHk դ!w4sjh}(\= PGVe r9dB')+-%NE/NCd71;=?^FAD3.C$e^]>i X|VADYCc.]NF&aUhj~ P>Di`^su.q__W، 戎~瀞|}n~霞z>꓾CDNn..~~n瞐NG FLn^L^֞팎] DV`=CDN~N.~D.oHP.w-L=D POot) Io_I)&O$dG 4o8B6GCGIK@?IEHOGUHM_WY_LO][IceZl>n=qPs=uy_=]``}_lAo3քUTU^ŚUV]~5'lt=˦;&\a s$Q\tKVoz2`O:i'Y"挛s !.ZhҥMFZu؜ D!@ |в/r^yԌƆ s',ql-=[4Qvasuݿ_G4D ʀ  Zv2V;bc ڢ c<|"ZOAWdE_T>SĹ#DwhB*p? AѯlXdqVIRPc (0渂$K 1چ3 TG7߄3N9((# 4=M:r.5"Ө3sLE <4SM7SO?5TQG%TSOE5UUWeUW_5VYgV[o5W]wW_6Xa%XcE6SIAYg6Zigi i[oeĈh\sE7]uV'qׅ7^y]Oޥ7_}X|(_&`WA[fad jyb/8c7c?9dG&dOF9eWfe_9ftfoF) Jak hł~謄h9izy#~e#~jɽV@Czjnh.[iuJ>m&;m:nUXpFÝ\o$冚EĦܡr_"O{W>{Wzs&Yw>hg\ug&}Ds{_){f54nuԟ|t3* mv52ЄG:'pi.B957auCЇ?b8D"шGDbD&61*R8'Vъ) < \ы_YUB0ьTpAQAύsH:qqD1qUyc =<t4,o(HVҒ"ϚKvғe(E9JRҔDe*UJVe,aYҖe.uK^e09Lb Df2c.әtf39Mj2f.Mn^s3Nr2S$g9tӝl+.Ӟg?Y{3?:_ԠAՙPbs$;B,aPCx\ Pn9 p\.P[2h.0*$5),l`,%- rMѐ'}N{S^V0]TXz"%NU;%Y"U JW֧ꔧf*ZV{+5I.$' _P.1ftiT)*t$^)C+ǐXZ!WH00 }L˷޲|mTkWS ZWʁ }jUKX%p{,07t.t!ҺMvm K9\ rexK^.qu L.B}e9/ pk t$h@u`.={)^XZ -9һ^Vļp,M׸MÒįqWlcXvw pݱrz,Z#Cv}ʓǵO,d5.VO+)P(Uys.-#d{kgFGe4_U(@%OGz|cXX:콂e꨺҉<)lnػtiuIs՞n :xtCW rPkx`Z{`:Sav!3`G͎X A{Ǜmp59㮵kYn]GKWӨƵKX:<8,Ok֧;K[ "_8KeO\-G9{r ԭy|onK@@?91u6OЫN x>Ӯu^O9}a8wnǻn.e7=¶ |w}$refaS7˱<t- m=dY>q,hCc3Oaaѿ]6] 8-a_c ?VV2ur]Mco~K{bo z 9=c`r: Ǿ?%W5xX=?&ss2:@ASڋ9,| 䶆@ @+Z,&# 4@Ծ'&AznK0\ZY[2*s3 ~Rk.R 0uS?(ӱBcZ±u|*"C*37° 4TC6\J=,9;<27ԳpC@+ BC50L&M#*K2ѳI|%u xsB6NT&x(W\EZ *]E\*^4&_$3`Ea>V'b&dD'e\FfiFjEjVZFF G+;cKgFz\^u6{43|3}3~VȀȁ$ȵI!8f X#N*ȇHĸ ȌH蛊qȍɑ08VadI7aɗL!ə II$ɘIV1$\7!1 J,I9I ʫDɪʮJH$˲4˳D˴T˵d˶t˷˸ld˻˼˺˾˿K$LDĴTtxȔLʴLDLlIKDd͠iלՄ֔LڴMl޴,Ll̑8H!ȔtN M$,5{)Ȃ?BuA5IA=BmC @ԢSPGTDTFYTCӦTH͉SPTLUWOG-UK5զTUXP]UYmUGeX}U9KM\T_U[ubUa5VHEVff]i}m}V`VjYW1FiOGtEBH"((|_, -LA5ݣU>L XyH $ V4^Mh>8nvav0@`=I琚xY2ȎIn69a F,")H-h+`9iVY p_1U=铃hH\:A&B6CFDVEfFHIdJvK$0$ȔJOd L @-AM6"@#8(%d,Ѐ\ `fcf eNH%H^$Axe# 8B  ) @,hgS^Wn$#g$8ZNȀrs 5Xf*3`655Hfnh$fHjVfa(|X6>@n `hyg1$$M ha H詐"he Pi> Xhn摮$Ph>iAꒀiPj`g6#engf΀xknF>"yyO6ielv>n l]nI#Yk#lVF!PRζ̂l(",ll&Φ:^XXF^FжmVPWg0ʀ& iھkfeHȀ.l~jhFfHf聱> j(qwFfnp6eG_q 0 iVq(ep.ejtfXfVAhe.o@eNmoGh Xm(mmnWNrFߦs `nr6ǮBhFt聂%*qJ o g(qpen^> v2d1gBpAHISuyuY]Ǚ[uYd^_aGdWegpPjv/((X"Gv+`lOmnv0JhgsjObOPܢv+H{wwjO8lvpxt06x&vl/#RKIcc8tτ` xa!z!IL / <7@:c q`^@mfn@Xc8`N{yT1z"a k-8 ^ 6Em )韚 i2,`N| i .6|6 _z_m_膴_noݿ¡$qvho:8}`IwIz ʹA ؂A$Ӈh}.9l~O 7p'?^\M濅K7^:^-Jݻz1 A{ MӞRF.l)[6U]֟]\s .uB'MDPdA2G!)46%׉"-"MbQGb3b%X~{UX1&AEYx!& 4@x7umҖ%5uPc 6ӍiK'@XW5v&DHpԙ/]S^)y(*(Z%USNyQMjz!V:o詫:NA/D<Ͼ;tǐNB0P;=k:k}݀^p_wË{㫿~Byv??t~?(@E< @p| #* r C(vP$ (qFaDR0I\"O(RS"7x,rQ["9xP4.ql$Kv  n;BF7Q| r943.pހ B \94rX' EQf!".A|.<ܤp}4~ a0GyJ*r.l3Uhm+8Rv\ig`a e 7B nTը 9BiiZsImh C0I j\o1LbEhьrpŠG?G;`\p%)or٨`6(Aw2"UMsPt)LeAr8t/QAi?P5TVBN/hSn]W3A9 tdB72/UF6.2+>ZV> P W^0#L~ C2:UJҠAqh fvA4 Zhֲ4fOKԢv)(]Ѫ]i[[zv-jsZw{[*W=syWօnlRWwq]Mv+^׶-{kJzUF%WYkסU2[,phY>m_η / HIa7~1'mEyϛ2]W7QG&8HE'yt8!pQN\w%;8 Aˍ""1?,ߣcr \69s>:FɁ\u1/fрZhVzLLWR]p'X,f?x=}hOga"poiFM+S»vIw>/SWs>/~{ϯ+_?d?J _ . 6 F fD ADF _ : Š _ 2 AIǧ|gǽllk"k jLA4dErazaa ZaD'fXFa6؇' Ax`dԋ!v#`$_!NZU"&&@|ĽDxɽKzCb'ߒ@EƜ(,j+b'&"&%$ c$y܋zYx)b *VA[ #„<'5HFov+#c9p#;;G<#R@c:< $@$ABd?<#CdC$$JHJ?EjJ&dL$M$F$K$HFE8jǂcJF$OeRdF$NR$RT6%NdC"y(zB2(aa<#j!KS.C$Le%MeAeZe[F^Ia ^8+`5aabbac'"F/a+c`͢&aj&fVEzxnF3HĠb6TkV<4_6,'Ykbg٦rj'zwzzB !Ȓea,L+:LަxzK_}g~Fw~B_bz_]x(:@hJ('R`" (na`Bhf`.~~芢֨( 2( Ĵ.ѡOÅ0NiCQ)f ^tHV˄)B))^.i"TԬd)iܩU٩NV D)]<*6 8*V^*fn*v~**]ӨӰjӸ^ω^^,*܁{XA4A$B!OFA ~J+$+`A4 >NA*̵+*+p% D RB+rx{+RAk |nOݣ]BGAdKȎkxñ\,RA:DW,T AYxHl 6@J > 6OJ@̲nAJ6mB/'Di7>R|x8>)W#^Kji>n\~#VM)˴{>>@۾>%4?'/?7??=\QY½\iq?3\yP Z?QRY=@ 9 6|-D5> L:"Je DC5)Ĺ1Ξ?f*_)1*ȩ:yVBb1Sdof̪ڶEZ ̦t!=|FJgs><2Α#q3dF]ǖVʖ/EnYUi!{.O<;/= p6ضhMFRP?S?%!;bK0 +l" ѵ.Ŋb /BrA "g8/1!4H s(Ԕ28H 1cot)=!,I9S'yB,B⫂@" ! 1ζP$A*AeUG3AЊ2QN!}B4$iE55FT;9좔Pz5RO=V"tRPBIU؇v([!] I['"rK,ÔV]"QmՔuASOeOkCDHTg/H+eI`$`XJ4aPآ [N+(Z3NYn-`-9^4 8f^Gf(}~蔼rwl4V܇$S̪ yܺI9Č"I*F7>B ICrGqS2jю^2G*&K׸Ȋ,#Z4$"YD*%H%z+Kd6OIZEN%119汷(EQDW|1ǯFU)ynL.0Rx&tq$d)$1cldK7qӜTqc2ɷ,%ZNněT<ZO7ZKYg0xIdJovf~:UU6RVX*6/mfBy M.vC %R !`ء3_ѩ9 ˬB::ժ[U2 ,ClS eezҺֶ*eRXj׽'z='U%bR²GS%׊P*fX8QurԬB(H"iTSD5lU$glg֞``B5NPdi-,ж3*r3TҝLr+27Iu]7E*{^7oTv[ʦ5}W2kK,"7oA`*80 ZIʀeak8? bʈx$Ɂ5b-vacϘ5qc=d!E6򑑜d%/Mve)OU򕱜e-oy)! "03ќf5mvg99Ȟ( xthAЅ6|<ã@!iIOҕi 88z.3hfQԥ5MjUZխ>]kNֵ5glw;ݞu{ x(Cܝ>w?^6c {7 t'>g|G^S|_N0D$ a؃:ԀBR l^8z"S3̃xxb<ǙkuįoGˇy뗗f>= w5!06afolCgB3F:37245K^2Yf3667. s6O6}7]38uS3SX993:s:: :;;o <<3=aԳ==S a4->>3?wtM??4@ M@ @>`??_??^@@^A@^E@[IAYMBXRCYVE[XH^XJ`XJbUJeRIiOJlMKnKNqFTyAZ>_:d7h4k3l1j/d+]*Y*W|*Ux)Wv%Wt$Yp&Nj'Jg(Gf%Ed!Cb@a@^#==F:DL5EN4FO9HQ7MU6R[:W`AZaG\bK]fF_jEdo=fq8gs5hu4hu5`4T46222222222222222222222A;\IdMnvSrqWuk]zfcaj]l[m[o^qcuixrq}_Q~E~>:888889<@BCCEBBAABA@@??????@@@@I_w̡ѸĿĐȔƗgWRRQNLJHFC?9={=|=|# H*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͎`rO@ JѣH*]΁?F͘SSVʵׯ`ÊKȧwn9VUnՈ۴e˷߿e:KLTsm[BnK,B'Tl9ϠCMNs^yϸG*cȚʆxqB^M:ȓ+_μ9sıuC.yܳ{8j/c:P^^ۧ˟O*Wo[luj hny w\AjEU|u HVy)~7*YgBXމG!:&hy5x"iH&@IcBB9wVewYWg#hJl&8ƙvR~G~L  wh9n6裐B`oyh'=I|F(d]k?=Idѹ)Ge X)^ ZkQnjaZAk6|J~,_N瘔[(`Цz߻+aSt*BDnZ]],u-JҖ/Iݒ[G,g?0 ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z`,,0deܠg@h \ dȂ~HfE  hܣ#(Hя)C Y ĐT$#wAuXJZ L@"U&nucHAʂI@0ẓe@8 ؃@䰋Ye$0q*]] "y[, Dn p;"yf4uD@K2jZlAC  +B@@9gDAi4A Mg$>S{l+ђ4.Ks8zd)$Oִ &EJBP"$EMy:`G$+ ] ßI*NapRdD$2>5Sek$F֥"ΧX*F:kA*:c; 4E^#QV+F(G\~uE`SF9Bb L&chelA&q i0):VmqĉW"mj{ԎJd –pSZn4Mm1z%ZN! % w*D+u5`RT7"$?k۰Cҁˢb -["hQzs ֘#1 |.MJy08gl$+ r)K 6Ʊ d d,l? k1 }!MY btm]⁜'<.*dw!B]Ap$(bg<$Ѩd4A89# Y,?z 94$~gMZָε]hZR3@biAֆ!Evl ]hW;K:ְPBjb(~ AfFwktmnumGmErs-.7vpѬrLG< ?Uos0< slos{sD59y+I'Ѓu; bAn#k c*9 r{E@;Һn/Pn=N.%,{0>;(>#_؏rS$Ǽ k~נGOOOSջgOw>{?Џ/C>!ǯ{7?z=p?/0-}W}A p 7(h} ` ` Pz0`퇂*x}-~ =zH"z>8/= g8!(=0D8PG {9 d 9zAz0pPz G z,ۇz` gBh&z9pCxz8|8W{ zz`腪hg4ЉEH ȈzІ؊-{  _{=Ȋ"0ozt8}W{`z`(WH{ӗz茨g !Ń瘎芨 Iz ِЊ *z{Ȑzوz8vUď $َzp :|הNـ'< ،AyE %_9Ƀy@Y_ `e={9 \z^zbY݈zP= 06iG@@ 4И99Y9YIɚ隝Y3yȅHЉ@Vɗnz ɛ ҉yڹىz9ɝiݩ ٞ⩞Yyy㉞)ə#Y9ut`Iɠ jgɓ4ؓizZ يPiJzwE؅D )i**w ЍPh' IHUWljס<{P:b'ȃ9i ʋ{P7&z=@ 0rxY*| H{nʐ~yhWQ9+0QjX@*`zʗ9d{툪9  Ȁ3ǩN9 w  ͩWs:{:=@ ৎfJFIIȡ*٭:RXVH z{0 7Bh~|˱!+#ۏ%{a)J⸱${:i8ȳ1K9)2~B[GIz@MOkQ;SU7KkI{\{^`D+zawt v h@myyvnk1u{y{;8}[w͖m36ywyL`LA_!gxkoep˷c'xkr+wy{r}pn-vGvG'ky+q 'vsvy8{`h ϰny[{!ûכ}#{oܫ v[3yD! upu[uF'[tX2;tWGuQGRuEaV5Oǿ 5w۶wK2;[{#;K7+[^AZu gyk׶x8kÌCfp F\w[Kp 1뷟O7 ]k+*G¢lprmoqsu_N{~}с^׃NNЇ.σwґғnҕnӎ~n؇x7Ӧ' !鵾VkADGvGzGUKJtQf^7T aJK#KeNK@PLMPUP?O47!nYNP P /Q"#PՔ>QhKw;R;&6dKe7aTV91W"_U|”uUVgu,W|a%OEWvRK2;!VYWvZF]Zt _%hFE/7Nk^u^^k!Ȱ^^_]]5_QD-^ĘQFЉ*G%MDRJ-]SL5męS'A xTPEETOM:UUԛdV]~+rr+džYj˞M+Vܹ=CW^}X͟ZXbƍ?Y!Ϡ]fdΝ=ZhҥMFZj֭][lڵmƝ[n޽}\pōG\r͝?W 4vЭ_Ǟ];JBo^x֤L^z큳K^ǟ_}/@Qۯ@MdA /1YB /0C 51DG$DOD1EWdE_1FgFo1GopGr.v*J% 1H){j(IDI(ҫ +/JI0\I7LsJ9KM,T:ToO>|<œ*( PT3 J&4P OB=T*FR=3 u'_o/XgEFVZ_ATP<\uKOX8:tD Z .JYHڶ(]k]V\Clu\fߚv;7^vZZw*ك~mjQP3TRkUQE-eTMŤPT18,7mᇃXX4N˜+R56{bi}}fKfF:ifi:j::k#Ѻk;l&쯹6;mf[mۆ;n~{nﶻnBT Fj gz.^k {`s0fy/a/ kK'>}0 Bkx/|!~v% Q7--:}X0b Ht T*aќ*4ٵ4k)e`v~g֊*Q4Ka ulkP)֦Ҫ&uX}ȕm\mԴ-@X(W>_Z  ,Y dmg˂Vmg D">*M ٮti9YŭnMDo`jcT6.֞; 7IۨUTms JJM-yMŠwެ9-prmɯik^jM/{[' .pt`pfI`x.1`[%!?ZkKXq/ b-4GHs¨Z%mk[^DΚd˪O9N*_K27vefWk伏9z䬅crZEO4G_L,5~3YXkz/]La/w&2c) ^m1v2@5>}Rr.,&r}sTL8y1K{NS=UTŬ-jWCY6}c ټ^6*qo6XR9y+*u[0z-wJHw԰O_`#)NU=D^XuQ>x=U"hrd|!p =$6"~q.k /NVwP'n\\ֿvqmf_(ϮvRsl*ӽv|?cێ6/>荷/y@sϞ+*SVG}Uz=WB( P$=}/ 1j|y 7TGIĠ}{F^RWIK%_Gr|$c{1)$4DTPt t tXpl[3@paЅ$zh?;t1A#!Oh% l^P[Apt ) H8>"BPB|[1H&&+AsKCҰ;=|;܋? Q3"C|05l}èr!=KKyv kD6؋QRĐBTlr0VdD%YCh}`),WZ)P8ac,BC@4@+7B4@P Cn#EY2tZF)(.ylDl#C4EC,}ǁBfT T=\̑ỊLC5;؅8C4ApE1]͘ Cb1>̳M ιNJILL(MPECW;xME1pCڴM,Np>@!9LOOlO|OO$M>ExM|E|,NLtL1@{I hP-HE e P  E= R=QPu ЋQQuuEQQ-E #eP!Q R'-)*m'U,Q%.Q)-1u]R=R/!]3}SO(;ď`PMYl Eͦ85/uS5R"C 4]T&uT-HSIe-U4%v)TBmS8ET0˧\OLU$lU|یUќS=OF\mYL`KXCAl\U\VUmVf}֗ViV:\m&8PG_]KaLr ЇbDj>x%JyuJzW{>s̻˽C=E>mGч_x]>{m%wX}]LEL֊؆%=Xeؑ ВXUYXʐCזYY٭Yً٘J Z٩t\mވ&Xbm^~(28xf^|)YPgoejL_M[ݱ"FG5}`H~@7g[Y]]~ۂc۽\m[i`*V>]ih[lCh_p߃ epCU.H@%c]\&6F봖TN=Bxq 1:÷t^  n} te l8U<6 hl 1;ӼNN6dX̶FKa Mu@mVmmC A@m{mlt↿;C[XnnIo)&FV^ vF?T8a0ɥh{h&[Z&g۲=pe[K2戗`jPme\ +F9gu,}84pٽȨ]u%'ވnhb^z^^ !nHfNG$auב l&Z>&f6 Vry )tN!&7 s1Ǒ867Շ{ֈ9F%>rN$ȠSGTWRYHF1]H;ed֝䃵[3WtcIacd0H=&?m?pv(dgNB6d@A^dHo U'TgPSu+GCxj0O`<`A_Xe5e-^``asec.u(/0+`ݕ'h.zwYq%9(yZiHiҍze  i= zpoju _{{s'Gu7!S?ǐħ_R|g/`cu|ΟH|}ψ7}gff؆mG}p 'v0W|_W}H~RhO}_}W~}vhf~ާ~~|?@W~7wX/m֭s „ 2l!Ĉ'Rh"ƈHaܸc$ Ef,ʔ*W*$i%̘2gҬi&Μ:wsKEz іHc,4PCvk֭jZriЧQʴ*RNٸrҭk.޼:OM_} 0`…OD`'Vٶm왧dʏ O>LZajW3,زgӮmVN*eYH.k3dʑ5؋AKwZY;Ǜ,K6xPӤ5,u.JݓgcuaCn} * Zoaz2QQeHa$0 3t 2p4xN"[x׀,8#5xcGz!=x])8$Ey$I*$M:$QJ9%UZy%Yj%=x%_F&ey&i&f&q9goy'yi}' :螄z(~ Cǵ(r*)~NZ){R楙z gfJfC*.⪧2裴BڪjEtʫ2к- c-x.b&tʒ&]ʂ܋f2nS̳=#B0n|1yyu)kٶjw1R-m%-\?+x?d t8c-a>xaz9^::z:Aܯ.yidN U:>aozIf s'&ZQ:8|Ǣ8pv:484O? ϏPwo+`$? 0_?"P7E0 1A^ ,!1']A^ {p0a gxC :p${%^A/o˹.w}isdڡ@17 q f@܈F5Zpj\XGq`/倓2u@sf6?-Lܗ8)"+[(.Z:t3g6uM.a6]K "pn391e&+\RP;.Xs)OAO/"Xe; 5PZ(r_V˼dN2UāwMh+ 3(iS' xɒʡM^6%7jz&.TeQل2 iz6~U+zWdM_Qؒ ht +_;†s,PJVbV% j=gCYr=-jSղ}-lq$Ɩ<4N1 dC~[V!-rܭuq{ڦåno &n$^r숃 Nw t{]w/~Km|oC&ݻP< kH{,8 /C,O.ez8 ~qL: شE*x!=1?qr!c.H愘Yh>s,g׶9d37f;{<<̄3B}h(υv W.{\9sӥ]CVif,45}dP9W^İ[4n;>מ5\XȨ.qbQØ%V|aRkπ;Pin-qob:7o37.?838#.S83>"Y3g{&ωϝt.h0! Y qE 8C 9=1 tB>9ԣ~{+Ony]\a5\?$ D₡G.8N/ӥ/5r[0DzA\ᇰ3M8H}lO8ҁ̣|`z6~9CNGBgB} >>P?1b8EA/;цv4Hbrd1[_"6awlOЄ2ӯ~e;W'?^`_? BADm]nɚ~ݥu-AA("!!D]$|@q =4`YW^r E B%B\^ѱ!ƥ]6>!FN!V^!r vv~!q! ֛!¡a!!a!Wa!  "!V""62#F"I"%>b%^b&&n"$v""~"(("F5*"+>(iۃb%-!.b $/!0#11c&2!*"4b͖\-6# 2#77~8!9%:n!;$£>c4BU=b)D<c? ?;62$2"<*A23c4.nn ?:$lrFrFzdHGd$F@dKK#=B*zo]czӅAV% %e9eP"%RRYS%|0S&A,oae$ SZeVBU:eXrV$UXvXZz[n%YY%]YdZ[ڥVV]e_&^eQ>^\FW.XJcea&d]efd%dd&cƥeg6&h_e&kc&ij`m~&miJfef$gfpYnfkl$+5c>Ƥą.a)鑨iژ^B`$*_B<(0Bi`U=m܁("圣2 !\]^.=ƁR*i*!.΅V*C**zͽ\^>XiM+D#@ C",iB*э@Uś*)0o;iABB5A݆ BD؁Aۥk$sB]+k$+"Ĺm %""k>]š1k? +Np.$B(##*kA@ŵ"#1 :D6+"^\BkB2m,"D:-BP-BX:mFBNv`^-ֆ`&mضۖ,DN.Eے(r 6*^*>$9m-9kfTmt*2-x.ީ.BĮ.n*빞"-j-LYdR> W"[lA?@.]: Ղm,B蟺. FN B+Aq? Cq$H1^m$lqCx1,f1Bog, a+/D)BxAB A0B0r>l$@_Aԁ+*+##$'fr$l(C$)*+#(F)m6n1/#oׄUr:B1/{µ ?DԎ &"Ck:#"<!9ӆ2.*39sƆ?3.,;A=F>_B3CϳBP^2ׄ!/ 2Brc4} q$3=!ys#79993 VHȜHyx4854LUl۶Vu zڡZ_YL[c٣6x:4wk߅1ƓsehWW5Xe;G_u۪%C['XzlyY((ۺ#.OC;㺦Ch%:l{h W{C7r Db*YY$;m:W,A#ngr;:'3Q+3$'f lk|q.v{<2Yȯ{ 'ȳC˷|bocS ;;Ŵ}^;s+3=k/<;}c {ưK[{3֟7D;I}NT;kzE[$ Ǿ>>dwbV>eeXYȊ?gU?+?ZAO35`?|(*h ˗/Lj'T>DF˶ K?P@̛gF&TaC#a+2[ԃ Ѫz~!։!{<\QO /alG/d7ojR$Ʌ&Qd9LGFSY;~ 9ɔW,kW+^GKuz@W5ӳȱjj 7a!uBlYcU{reʹPý}mU8X{/SukO28Av  Ҥ^c|p=:'4qՄ9q:fӏ.]hK@z(po?=߀vdKP<@CP;hnD3LB롐B *$Tܖl25ݲ{„rJɹ^rb3 r&\,RƒY=8MpKi<15qJ 7 sN+43'7APB!-O%@R;%Ej2 K6lQ;9t.wQM?i1$N]РUSCB=hT?m NMUTPH!! _,i:TO, 6!/\L1tuP#TA:U<.UoA]؝1]yI_a]+N8_lBލX`/L:NHofhe+wf$:9n˦k֏BV"5Ii4}ahZ?rfp!L0=7]Ztvz06hAqZ!Y; 'onxrk{uH 1G3?lJhVvsr]vCWIJ2mI]rW {gd C[UbbC1RщB/Zq#(Q/cȵ*!iLEYϚC RRb\\H#.2`jTX5ݘ*@)[QaOR]1H(bAմAuU?kuqkz>smlv{ ?WٙBj ,'<ƚ`j ZX-4p{2O&05<5Bo5/NM"!mn5n5AnAH\ />XP2Pa5j SHxpvx&5N@ N p ٰ C. p0p- m qq Q1%1!q-w1q9q:ACq9QIU1KWaqmdg1uyUqGuj D-{QQm1Q1őɱBjl W3QIq_/ 2 , 2"'!+ /R 3!#;r+ A$#%-q#%%&Q&&&/v2'm(ˡ5)(֚(Y#)r)}R(*))+WC*؞R)EM+((,#A,ߒ*k+ǒ..r*R-//++..S/-+10,)S12 2('32/#33-, 29S220ASo-aS4/QS3q44O4oS7G35 38s4ms/Cs6]6YS.76s1-ф Q%;''8pݍ89y 9y!9%YwٵO0|A$ P#5גopf6LO7T5-̵{o5.wWCԐA!vZV5w#At5u`NreYgAtoYWo_GK]cA6ZKɍ(57[a 7o6 qbfUW_ Wl _:`Gܙt[#fhKeG#5eoe;fI]t'dWC=6m/azƶln6&p }yn]ZjLh:iݾ֪ʢOlw:ͪhm@ٺBA|`[##Kr[o55֢Evr [$. _VyWvm![E5X[+5@WtstcGo G N 8p|}#{ U;Ae!|'7^.TzW 7{Nq 5ٻP<| <$yvy|9FUYA“e8\Ԗxa Q'\Sa⍎a;=B]]x.;}9%l]A\9IMg]_]ώؕ}ٙٝ١=ڛ׻Kaگf]!,}} ڻ]=۽ܯK %\6 &םޥKࡋo'c!Т* H#~/-I0B"A B3,S/0aᘆƆ.H!K牋?FgDLd!zĢRĜzdBFD){ ;^#Kz@D!cޅH6~^~޴>["V>^E[H)wDJVX>AZ^&^sEa8 hZF_JN3f$g?Vbr= `a|Bkg`_R Llj\H%">*&vO(I("Ȁ"IhQHhG_ӟɟEh!"@q4h8F= "D!ˆ'pa!^|HBȐȓShK*cv iMIsϡ"i.ĥgժz!{,"[ W[WBYm&ź*ZV a^Xȁ+Y=$J%rhjUj=ܗW5d臤Kpn) '28ٴk۾vܼ{{7ċn-z):,:]2i&hlV)g}m07ve3}*hFቌuҋ=gPgb u2 2$9eOhVU=%[xJ'j넹fja&.8,*i.;7-yV,j-z-6Pw.wd.ʫ, .چo;k v0> qOLq_qoq r"Lr&r*r.,F/Ls6ߌs:Lnb t#,tFw| 4М @QK=uiUSZou^}1DGr52XY6jg]V-vvߍwzw~ xNxx/x?yONy_yoyz袏Nz馟zꪯz뮿{N{ߎ{{|O||/|?}OO}_}o}~O~柏~~Oߏ p,*p lJp/A#ăNp5p3Bp4! cHp&| w τp>($*q~;~(JQ}ψ!,2222222222222222222222222222222222222222222211111111111111111111111111111111111111111111/1(((''''''((((('&%"%&'(&(((+, / 35665212 3374?F5CK4EM7FP;LV:Yc;_j;cm8hq6js>pnMzh`ejbohovhYL}A~<9::=?BBCB@@A?????@@@@@@@@Eɽ^г޺·^URPONLIFB@?:45y4o4j6f3`-[~-Yz/Uw7QtDJkIGhKFdMD]MB[G@\A@]>?^6?_,>a!>cAd ?d$Ad'Ce*De/Be4@e;-36-24,26(0>&/B$.F!,F!*G#)G )G(F(F(F(E(E(E(E(E'F'I'K'M(M+M/N2N5O5O#H*\ȰÇ#JHŋ3jȱǏ Cxȓ(S\ɲ˗0cʜI͛}i򣏟&{JѣH*]ʴSA#eaU:NݘGϒOÊKٳhӢ:p۬ZCUKÈ+^,scq\PmYsd13#w!S e̺װcfI%O =r{2\3&:tMسWev}\uۿ/=鿡2$Ǐn_(7szYy w_{)tjҕ_EYpH(,~c)ǠAyj.bzUs`0!H^L6Py22aIWj^ &UZp)t4%=ȗ 扠`4n W[$i[\Y礔Vj饕rhf("=B'xהUqՕ$j뭕czik[onG,~6N鞠BTmsۛ "Kٷ.knjRRiܖxkk΄,Jjv,Xd'kG,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HLȽP"# *ZX̢.^^ HF1hLx5nl#HG6x̢;|IH6>,"ØE:R|$IZ$3IGV YXC' P<%!(C (S}#:@ Wb8+bW-GjZr6Yw ]0*3Q<2w\ۜpD:ߡXl;б Y":`_Êb6XqTbZ0 E' OP8tAU:u ((;CJR5h)Px7WiR͐P(å0LHSδcy9՝k`jR+NU}G-.VRkBJT+ OD8z'+:ʌBѣO(HJWCאZx2tXbc8Z>[,j8̰wa~('e-[EjV}hĭBl+(vj-M׋zmfuY(&}6QB!x+l_D*ߑn 'zxZ w ,}|z.u0rMk\ ?1XE+`b8 sH_)>Ī;ю#qGTǕx7z {Ǐ {#CYr<) DM^,\&o9BF f'?X.ang{Yn u8^{ s6˅eE':Ov]fCGz̘~3hMҞf4=QWZҙ5} d)խ63YcXZ֝QSoF.=,[L4z֩uU wP|6e`5EKԿ=ՔVv6krC8D1(LK" 5H%Ɓ\R67'6vxuYߊSCܞ‘%c̥qB10CQ0$Vx}D8 _2YCO >c\Cq2_71{I8|5[GîV~GXHbѥe⚪lnKB<1,2Vݿ4PEc#ȠAc=QE?Flj[wC(j( sc Uk+'vsa5^fSv"$HI-rQxb?c?~G^^ Z XZ 8Z~'gXCŁ@=$%$D&x(H:01I4:2 !1a)3PQAA aFH= Q@C ф*HL[N3=I ?A^8Jjȅ_3` 0`<Pdzȇ~@և)@H8F) ђ2H(IVYyXi]I_)a 9I@Ip[YcioIqsuɓLMc|ٖ( X2 cc jh?ևY )3I2h1șᙣ)ЇɚY :PYI 9 ɜ)IYI9ɛٝii䩞Li9y|cMyyiIi y٠ :YI L@tH)xd)i9ɣ=HI:0kyQ@JBJMOzQJSU:KYat(Wegji:kmzoJ[ʤsڋ(uyZ{qʑ}ʥjzxȨSI3i1ꨒ 5Yz(= t(BPHPBɊBsJ٬ T:p@ZBPXۚ$ժ Q"䫛t亮ڮ:jPhP,XFIڤ\`j\0I/h 0 zq ^jP@{[Q1; [(˰ 0 1Kc 10S q`Phв LkƐ` pGP HIYP+TQ+S1BKF{OMKgke d!Ɋ]kj[#KoKW+` Fh`[s1f8VѺhP۱b W빐1K\:kȹ;y Ӻ{˼I `dH닰[ :p118 q 1(\!Kt:0NK[pӽu3(2оe˵VM1 e˻C#*\S|1,-,k+@!E\kPXIܹe;6L4a‰иWj۴@ܶGKX ǀijd!_@p4mY1+ڄ9;PzKÄ,6ѱ~`E캂|ȑ̾c~ ~$+ %A Z|ʨʪʬʮ󥰼@Јd@̭:˺ 1˾E(?|cw#*\|؜ڼSÊ=*P؊YHvъ0\e:lY(+ΐ8]ӆ ѭx-:S[ П! S ]eAZIe)}<"]NA`QN{Q0OՄ8_}17ѯT]D0֫HjG, km nmA=GȖvzPhM\قM}'mӏ0ϟϣ=تڬڮڰ۲JΛЈ >{̈́|L׾m=:P*>m: -9 M=^$=M=M2=]ѐ>^~  >^~n">>&%~*).na-43^8N7pn@ ; =AC^GK PC4>T.Qaax ^0@~peഠ WDc.YPujn߻@} 0Ypw p@~f=%a.= 钮e~kN߾Pgߤ>n ^B.An~ྠmp^ P뀞鰠 N b] d0.d 9߶` - @ p n>`.ѐsp. nU%o^ !rU * A>^F/  s]kkp? b @ OF^9߯ 2ooo ^fp[knrme0郿MP z1O߇o/IH oUߗ_W1ac_eMwN߳ N/~^m6/Nׯ ~7/_pMmWSV튖PB >qah~,^ĘQE=~bH%M8J-%RR&9~8|P``:1PEꥐ%YK_s/Ypm3.\,sa/HE!b6¸Dz]{΍:5ZիY/nU؝ wb7^kN+W2ePz8tn0 !c![|-l3ז X!_ed&.cW:[3|9mm?hn~zac.L3sǮFsvqۧOm%/g t.:;?#/%$J+CX. 9Afq KH*|(rŖ^S(T({H1Ca/22;0z4}yIC9"?pS4S\F晳X":~٭9"c炀j*q鄚e9誛lZkH"곥]toUxX_  VdxFo/ܥ7\K4<,7=(SGWw]ȋꭿ^>"剪|?}(迧~?`8@ЀD`@6Ё`%8A VЂ`5AvЃaE8Bj_ 8 0%a e8CІ7auCF GDbD&6щObu EEbE.vы_c'B$!Cԁ#B6эolc8G:ʑw#4я \,J E4At"$>r%CI$j$(%&Tr%CYJDҕT 9DXҖe.uNE%/9LbӘf 1MlȄf49MjVӚf6Mnvӛg89NrӜDg:չNvӝXD>Ӟg>O~ӟ?:P1EhByP6ԡodC%âhF5щvԍhH RIUOԥslKejЙ6isӊnԧ?NSEuhN(d_)RgzTNU}UJѭT]EhO> @ƪ6 Vĵ_+A|χp!!~F=HX2֣u,>!Y{:uP!Յư1FЂuM)jJ?_U-Wc +ns[ C n@kF[pa>hAZn*t%K?2&ՅtG>Wޅ.xy+^V4&hg{Q67`iuf$](yv=e#`HXE%``+&J#4 0]xaR2x"G o9Jg2"ca C g?d1\`6X.l+E\(a8'񕛌QNe,S=ld&8oFW_Oe` ݫ|}# % Ɇo@6[.s\C@314/ц>/ao_SUՒ}5c]Y]oP]8yn.VɋaGM ϺŞ-c+mR֦e9\u^6UŶ S5hfu{Lc}'߇0M7uQ-?s-q'or<!zkW9J/~r<.]]r?5MqO|1΁.l7GkrE:>u#Juw_{>vӌ,˾vv0;(A r+\2׺=` @D#P8@cI0 hڡ(?-Ow`L獿|Pgc:@AjY=9Fl]H#g((X$˔oAK 3|Ѕc(05Tқ; C W$4DTdtAS*H=(!M?"TBJ5?P5(5 $S |+@:Yc $2YZ2,ChC24B ?=<$@P.PI.;5P;/L\PSDt>+[K!48CA, ˂ ?ˋYX/ȅXUYlY\],$=*7#RD>=4NAi-9HN Dٌ C-}S bS<ݢ85O= /L8?%T-2SB `SEu%D} I#S+"K= 1("OPLMQ5 k@SUU2ӡ\XYZ[\]^_`2S QMp&-.4%X۸2R "q(MsΔ0j'O+4 -VcJMP~DC5dC78oW9`sš/(GQ,8S(@T=<{L>>5]@.dKpKd#l[K޾]_3gr1Lα-QPM-5:uޔZr\^p%ΞTGݢ͜B- >4 ^|M},bUp!s(# ~O:xQTj*Nƽ YE]%[ZͣZ-zc a$@.f9NZZϙ OA6d%YPac$~]ʍsȄ@Th  -QGOeXl[Rd4׏ڸn &tU,8':PUlET,/Ο~=C PPbf\ꘄCRPfc- Zglmnopq&rc5^3.APu^^-Fg5"ygaDNf6~&ND "h`4=p?,Zh_anp剎*Envh6U |\Vfv闆阖陦z&i6VfjFꦆꨖj~ꩶ꫶j&F>fk^붖뤎빶kw~,XFVnvltvlljF7xB7p+px.0:pUHZVx86m.j^p4F3;7@l`SH[B0nFoVfvoǾ5肛9pw50it.8]WȆ6m j^,hj3@FiWwWPȮwq&pϾ?jq+&iXX^Xin ̾rWi'r)Zhͦ` t.B5n;CEˈ,i+rq@_~38l&'eprstA'jt?Fvtu!mi`q`iGGYuoG( [^uuu^'[7(v`owveGv`]d`i_cchwlvs'wmwtovvvkgvsqmwhwlw}w|x~tn'xlOxq?xwxwmiB7#LOw1/xrgoxx(\oy0uyZwyuG|_xGx{]z}x}/JxyK?piqpu(wvOWV_Jqppo-B Vt0iu*N|"_j" Rqoj|Зjwo_o63Cu|6}pNx7|'RB0toݗtgo~Nmv.#wp~.hQi50^SZ-Ku)]2l!Ĉ#Phb 3j#Ȑ"ri$ʔJl@0g)&Μ o6|eΠ)y -꒨Ѥ'*m:#T:*լ;rk֯`۴٤h]vfԨSߚuKݻ4ɷQZ Z*SbcȑN\e̙&|ճ_ƢG.m4ԪWn5زgӮm6ܺw7‡/n8ʗ3o9ҧS^;!X;Ǔ裆iϝӷNHb? 8 Ea2P|,H V!j!zUxу2!)RtaI~*8#5ڸŕ$g 8d/n ~D*$MʘI(QJ!RJ VaEbh^jeV f&mV&ǹgy'ʶ^=!?6x6&$(Z (H&襡:*z**ZꗱF*z+k+Ҷ:{,F,:,J;-.[-jmr-V뭸[.窻.T>/ʛ//|0#p 30C<S//ޫ1Q F0|alDZ(̯79?rEkljjc,j`EPС .&AA(]rQ̱28ʦ7{'G^01&F%3kL:c:Exkp:0`؍:*{G_d/[ͪP7KK>cKB,˝ӄ$~o[l!p,`XcpݲJhKhB'D-rVǢ!Ef6;! y萇$?aC!>M4XE%BZF)q["E31j|cG2q],#ט3zq{ch),9HЅ,T!F0$d"XGErъL g|);iMf򔂴1k3 <"K:Y:]T4JXQ\3WLGFs~\$+M.^04!")B߲ >s}s綎4!ΜB]2YD,Jw=csJS70mDJ;08&pEYkrƒ7XFZl)-+R8H|3jސz7gsA?x^i̳CN8|%tq%ae{N͂5amm[םΕuݴx`P5Z#\Z2Jw=a-cYN=a'+؜V}@3ꌴ730kvֳm-jM[Ͷgmr򶷾-p+#R92Mj]~QRֽn~1rnt MԍI;/zӛe{{^W﫠sKo} xߋua(#, Sx4ޯ&Q s-&")0x&>1S.~1c,Ӹ61s>j#!׈42b$a4$+wR`L9@.k`H>TqU,:C\]ms/b>"l沟9 <Z!,gΉ3l5@yk2E\S#E=IjI$ hVC(]$bE3oR^Ҭ#GQE>whZ| }@9H.p"bv5lhPp17ra鶼)Bq"6ƴn\rpBobxCa B R64qq ܢGUͤdcEtS;֮ "B'нnG@׶Ý;s6Et<]]9-v@d:/Rr"8TDOzE^t|> ^<;c4`_q;9:E0/_H%n[~1yȧ[>EӴ">xsQK<.gA.}WGIž}tZT c4cɲ3-&05ϖ7|Y̟t`t@-`Eɟ> X!>0 9 4nU P%5E$MG! !KA f`&Bؔ \aYD@E]E]]ҝi"\YV]EbaE4>,x!RFEt!u|E!AE._Y)eܵD]߹jF!F^yXױ)II4EĔi+\BRIJq^LZzbE"͕b֭"Ebi",b(btTM5MK/'΢(ua..ci_'Bc,jaN_UPi⼅[\q#01<,cxDJ&P 21EY1Vۙ;dA,$uqDr5$TX!dFasL$U]D$ERZIbBjtpdKV~LEhr9 etcE \OZ JYy hpex "V%YY%ZZ%[[%\ƥ\%]֥]Pݥ^eCD@QE_&a_>Hba.&cv_n_*IdGee6fap 5 h`5hZi& HiBkjfczf(l&nrgfPno6W^Wr&qq'r&r.'s6s>'tFtN'uVu^gqeh'pxg剉W0ɩEaQQz#'t'~rrzh{'oqd\YET.)|{|e%x$&Ea"cp!r{nX8Vg}9Ʊ~Njv(9YicEL\E%u\xPz>锞w(v)芶ꨙ z^(}!jWdF@ ^FFZGLb r)R'c癚#:|)>Ggcoq["EA5)nziVimbjfEkkF氲&6 &3ŦtRN>:#az. ӭ*Gv+ **v*i>8jZ*,!j&Gg 5fj=٧~6G'|,Ȇ^boFh *j~",F)lkҬ,NWĪ"6۫B=&)7bfmZy'{+*njsg*ꆚ>KQ| ". NzKdOTdvGv)FZ歄PnX.`ngwEb'膮xn֊.Ȣ.|ֺnw..stQ8nn'o/6ol/FAbVRQo$!Wf`M~Ưow,/pN07?0GO0W_0'cFMt;0I^0\_K fGO ODGp ۰Fp? 0Vpo _i BH!F :Ӹ\@t,D8Z@-B*Cd*Bap:[qAY6sx1Cq/DAY!GzAD1q$2%*\r_dD8AC4232$;$r%!k1"+;2C2'$D+Z *r/B0,C x@6c R<1ӱ'21D834K,@,C-B1 @/BL 8"_:8A/..(@+,̱?c@4:A/DBB7@G@Z?/+vb2B.4FClvtB5ChWcdDf6g;kvh6oB4D@6oJvd+g6r#ovC8w"/dj7qSBUA3B`7k3C|JCxA0l3___fMpIzKQ[Dee_y?s/C5;t@E1Â;DuRGn3 AB17D?8xqO;&pCx8&k:BxCw8Œx37Pt8C`xd@D;&uĈ3|6LOP3hdēk83ih L60T6!(xsk8^ÁO'177.~b/Ci+@g8B4퓷(?7COg9C>\/?K߲s{C5@{'wn4cNByhp-t=\kbGA91»po D&Bvy΍/f|ƎȈtiIOF:ՠ5fRS#4mNӠZT2m[ Ig0LBatǏt;|rhԩ_5~H8`+~رAuORWuH45ؙa~D$=tcխwt*'6LC{mEmJ:}|;{^ =}Lx6_|կSWYkM2K A `Dp. )Π,$PBV*jJ7U Ea>%ǧpHy !ȉL%t(kDr,RK/'ǔ$<4lSI9sN;3O=_dO@M,֑TXGGAn,OTqw$m`KXF1$@1\:1HJZ+X ZRިbn- ⛚ҒvM]SY[&lS&[‰Ρ6Fήu[\K:9NoS>NuΨZ\eLn3s; VE*qGn+6AcEf!-_1]4&IE _X^zx{ X ~^l-g*!F pn+y8$oVX,X1~nlomdE6򑑜d%/M>ݵg-'OU n]|7e6󙕕BD mv<+,ui-}l Ѕ6 эv!iIOҕ1iMoӝAjQԥ6Ql,. >*h2$tnPdBq=]\}B\EӖV(l۬-mbJIa8T-I*N1 ukЂv,+;bErh82Ƶb kVhxpk+<1=1鶱w'E~ofa 0Qܵ-8.n[X]c8$o3+3we1FZD\$p;2G:՗n8=P׽v% ʳvh1T4o i񖝧 cXʇ`=E*[~opKž)/E:nyۢ4yʷfᒴqߎyqO! "o-f 6Xź1KY}V^񎬦.5imӜU^1tƢS [,Riڽ߭H-}b߆OG[-\oYƮŸTT )@b΅w.ܯg ꄫ"bEQ/ua+PePB ^J "qp-^pf]2nfPVn 1-Ԁ-bN.ٖ -DԐ pZ'L08PY@3`E/Р "ZH0ߦV/%15q9=A1EqIMQ1Umk"@`eq?Ԏq1)P}# l&1kĀ`q1FA1qɱ1qٱqҀAg*2#vmB mb V t ^*Y/< w PڍNzp?,\ -R q # x-lt%r"a~0#YA_mq Nͨ CQ-)o\AˈobRb\(("%%U2.WP\ϒ 0*-HDn@ .._4g2GP0+I QYJ0\ޅ[Eݪ0HOc lF*tྌ-B325e-njzoޫ" Z;= [@"y / Q4tr,/UAW1//00#@@ixAwsZ΁'Bd + p( f)12,;tZ`pk'Dt#Ho0ZpC/eL]!/ Ot EقB:3tFK!6H)U%~sY62ĐKO:eY^7aLFP uQQQ!5R%uR)R-R1ol-d/S= !# SM%TOuU1c6UaW+Vi5`j5WUq>TW}Ue6WXX5YuYYY,QdT6iiVksEs sstka!Fb "<A 6"Zw'`LHt-(uU#Xuavoߡ>LJbwpy7v_w"WKF%. BA.S;j{o{=BtD|2`-#@"!zw'\v]7~|!`}ab}vu~V`arpoc-cm:C ‡,>9;QH':(2@|D܍H\A^NUW )c4k >|ƦKHTnB}hǣˣ̇@<} 쫞s ʛ 1ƒaɖIf$kr(J^Ӫ! H`0}+}Aҥ*GQ]S=}59]_eky8xn#dxގ>Ty|[~͞˫~һag-*? ?!?%)-1?59=A?EIMQ?UY]a?eimqTC}D!r?{eA" \A_W?BV~ _V  UB!?UCa HAT2a ?% !8,""""#$%'''''((((((** , /1355556541%'.,0-05-;B1CK3EM7IR7NV5V_4[e8]h<`k9dp9fs5cv6W7:322222221212222222222222242T9l=w>x?y@xBxCxDyCz@~:8:>??<9:=?ADDAAEMQQMKKIFDBA@@@@?????@BU}əª˸õ}w{umhd_XTRQQPNKIGFDA=;610}2o3n2k3f/b-],Y}.X{6XzN]yvfxjxes`ockfgdeu`cl^bdZa\XbSRcLNeHIhFGiCGjY59S.7N,5O)4N#3K,H(F'G(F(F(F'G'F(F)G(F(F(F(F(F'F'F'F'G'G'I'K'I'G'G'GqH*\ȰÇ#JHŋ3jȱǏ C8`ȓ(S\ɲ˗0cʜI͛8sz4)$O@ JѣH*Eϥ8~YիXjʵפUUlBEP[)Y_ʝKݻxSd+DQlۆ5XU)ڃ%-~JL˘iU623&cI1iJq۸sќa -*ocГK}v1rD^gOa^lMo _v}aaךkN]xmץF(x{טj  paj,&]s*s!nqV8<[!\qQWIé l$Jt*&W}ѸXb0]wOYI0lJ'XT'YD f] hhi7~נ#棐F*ed68i^ hZuYeG骬bڞB"ZꝢMj+8 Zk6F{bafB+ K覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(V. gH8 a@!H"QC<$2PDbH:X%n`DCp0e<m51m|9vT#h=~c! zcC$ڱ@ |$#)AVҎ$3&vdC) !FR<#)cFZq$c.]ZїY%0B0+9If6ә&)M:VԔf6\򘉔9yMOӜ4b7ae05s`H.%3PFȕ-dqlA Qb[8I y JA(yA  T Ј341 (mXZ!B lP dԷ5Ig bk@h諘Ci@^m'A`#4̰NJ@p~7ȝvCDMfk=0Jn\ !HiooB H5m3"}<8 , !/5*Do! ~ - fB); `X9'xO}=_%s*Loͣ>g݅hгbI}!A~g Mt˰"H[]wH b;<>-K p\8L}4#ܻ?$>=.{q8 Џ G@V AZa@0H$YC.7gOfم>%cq2t~!O8/f~a\@X 2?p7Xx 2 HЀ Hj@xLԁ+d #G47m8%#WTr#-? @8nw8HDXFxHJL؄NXB6s O(?vU?0Y>u_VfxL67[؅^8pxjvn>ux>Qr{8Xx#p<08,m0ah؈+a RLj.x0wXDtC!mC88(؆鲋XIBy }WTb(HH֘HL| ȍeqh&8qX-ɏJaX0(Z I/DpYyi )}.$F!Ȋȋ؆xQ8-= Ӹ|y8DCy`HN(h"踔<)i9iG h(NA`hNbd.gis19[YtYvyxwذ|ٗ|~9Yy ٘阌Yy9YY9ٚ٘yi] U‰ éyɩ ٗ9YIЗ0 q  pmb@ i bd` @ @ fYc ɗPh@ ʀ~ ĐeL֟{i ]P:ٞ  ʠԹaP\0zؠ鞂I  *{ 0L~Iʗ,/ʡ!jĉÉJLڤɘPLR Le\ CZ ` { I LP^n:>k3~ Vd]0E` d}pzP g5-0 I7 u4!V ^ a~ _`VPvkgl;^5G1 0NVP_. o{WlZo_^!]A Q^nh.rV~$ՍQ%_萅Pr )qA鎞/x.F d.;uUu,js \.^z^~nꂧw-Y@ %t3a䥞L W{U.Zd[-1nbnq.r퓳za#0Nc6b67'&c؄` ? _/`1,T-`b fUC!@a7fO0CBBgO` + fc% ]t4k&CBK\:_O-Qo`1A-^Ę E=~H#ED2J-R,`C(Y }BIES5#M>5ITD R]3rV"Xe5!Zjٶ5 S&W>ĩp_3v w`k 9S|R s梛9ehG Fu(QA MU邼֬8`aIxyG˿:WEZ}pĵ޲zx+4ް~o2WOg?~W$ t> BP{к / ېA?1DG$DOD1EWdE_1FgFo1GwG2H!$H#D2I A %2J))>*K/sK '8> Ś)[B%Z|WijfZi Vl*E}&BA8LN;ǖR 3T)PB'(A8 ZH 2T&QZ9TT*DKt6Xa%VW%NP4qT ()V8`YS}agrw/ilXSbdM ͧ[haoC՜_c?9{{mRLqPݞږfafp٩g my{)0啡j 8aa]*4vk;jl%š'v*iY婥WfWoq%צ)Iۨ>)[D h#] G't}2$*beVIȖTd`ff`g=o2~+ڇ 8'mcbo&jl*SOIG'vNV v덽~qd{-oN9ŌExWr֔fA Dl `F@9JBn*>BptW@ւ+)TpmETu.8ox|6$+hi#> Me6{bh Җe.I$Jd.9LbӘDf2Lf6әτf49MjVӚf6Mn:=g89Nr崦9䀃9agLoyk#$NvQUXb"ꆳ-]]SbKoP"8(&皿+˜V0a"S3u" nm]\8R2$Ʃ)^%0Bw "C >Րvq(~EƳ4S'yusq젇>+}KLw3'G![! +YB|3-PrUL6e_*pinkX\= H*현,aLj}+jS]u ^1\Mj*%~Y_)FH.y;Q hǍsܔZ8޳_y:XG1|KI s7yus?W]PI.tCG:#eZDiӗ$ ݻAOs'eu{.w^W!{H6|OO)A^RX'uS|nW~%w^~^1|>yJcW?S(K2ރ?#=5=hk,:(@K=9k:sB=뺮!22X2ajA:A?( !$"9x@$T$ā%t'()*B(l+-.,01B0$3D344d6,C785:C:,B>ABA$CBCDEtBEdGGEtDiIDIDBMN; 8$$J9& EOVW8YtZE6r2*($+`!XBq+YXHBSgHFFj$]ը%P(,!H*('dFRFx(RRXpa@^(ExzFllS@̄ FE%T%L,͊D˴ 1L͓lBBK]E$UP`@T-?^﻾}>^3Ae_]\m^c](4ߦb^س K]``U)`_ nɳe@׽#p,Ih[U]+] 6A5ްmܻ%[ۍ8A#Z5#F&v'()*+,bR9b*a ~.٭3Y"K.YlOK1b6])y/5@(~/kc*`c>vb?&\`*?Q(31 (eJFeL>Aé(%*Z)ooHeJ~_^_?'4B3CD#Ea -fbk@X55Hmb\v)6 7I s!t_)sv4xHa*q8#{ sOch&6Xihi~iii"iK;ijj.8 L Yl 2:RvjjjNj"j_ac \걮ψkΘ̨k˸p= Lkakvk8lHlXhlhEj ̞llflFLvkþ!vsa>)^^*n*nޮ N>f>Ɓ>nn~nnfLo6nfofm&wVr`iL=9$_^(M(q&Zr+ذbhԗƩ?!Zثdʤ4޼tkKEfF2~KA ګo^Y0欹)^$_~Ж1ks;7"]9F4뮚%o֍[6h׈9Ǒ{wLO/o<^6]1__b>L$~ `xYm BrE&δ}iEN-DJ8M]XmaD A"ӉXc)bE.bb3":$ &/ $QJydZ404%RL.rP¥BF%(ɉ\I@FTMZ >7-[~9sYxwqkn^魻:ax-N^9 ?<>;N/֘[,,{Q/w;??sm|N:n?R<K< 41/tv@0nsޔ7 rj9 0" S6ˁ,|! c(6!sH>KF@")ҌR, mQ9 HAoPBD*%EJv41[4c_L<%*vq ONq("@G⓶ByÊH”W!2B) 3?Iә<ʁJAH[X@8R0m8`rWK}AС'zP~.T M56Up^s;G1fc׋湼];D4Xa'u9[ĒJ 7 DFL%+S"6>:Xj0J/DD=*XÊ4Օ)bmF ZG>jUv+ծH~+`=`2+m,d#+Y8.}FyяtDI${:ճqWZ:׿.cz~v=R_}B7*AbqQ* UB *.+`FPςx9H WF(W|'ʓ)Brp&|xx,O=-oq˞z-+xm|[?؋bG8 0`=W/S< g_qS4!??<4px9 aTa<`h0T7hllCy\`^^,ڝQ^T0< 0TI T\8 u] ݂- **` V A ^a-! Fa2<Z4xՉCǙ!fa\ \^!baC!b)V\*_, ""(.ɩ^\8Tj搮LC(2ig.)~('*i瑪i'h^n囊i)R'qVB QOBIj#&N%'!LrfQ^d==jǥm`1Fƒr(jZȝƥ*Bj]Ci*ɪVBZba*Vj.gvjje-riMMCNl5\^!(QC%7硚\"U\ 4B>)$$HaDVAkAa;U,*zFkVCr(~(ÊJlg.&/Z&,v,<|lȆ alT쿎$z $6륬B`0bl~͠lǙa94Bm+2LU\/ȁ`^b _ b(l߮-ϚaZn~jCtbѭn&n,n.+ &_b|ebbFvn9A^vq(@.J.P.d:%.N-dZ VFH/B,FVrmfUi>otBQ6]qi/ˁ/}¯ѯϩoooao 00˥`0)1p\O0YU0KWopc[p O Gp ; 3p + /po \ p oo {1'/\%TtC11p)qY 1/xXey[ɚmP7q[گXB1q+9|%1 7C  ԜP;&!U%DO&_&2Zv}r%H1V12-[-2/\20031132'2/3373^K1|@$)?37 OxC7x+w}x x8w>?~nG;>ks~dEȾcW=>@D9U50t4>bQ^:s(L#Uaڠ>}[(`>xBCZe aQiTI5X9)7&kPGf3GZd(+^. 9;{*/ S1 +(a+~3(ɂiƸ23\͂ޔKN|:Ʉ"@i+N'TLГVq YJOA UT1C 0@( |BQD9+T ZWKhZ QcL0pR(DpUVZBVX&@Vm[Р !:osMWumwWy{W}X .NXn{Bsa9ҔX98& a"Nhd08+JVa8OFe&9cŐ{ZkE(.vAZi4i\4[9`?c܎[nߎ{/o n g;%>D!ENYiti후xI/}4\5gt+ ^ۭD˶q^xO^o硏^驯^*{_9搄>Xq 6HAfJ!XJ) <1RMG!R bZKq2 DC 2X\+x 0>4%4~]M[.RF *PDBU4#Kr&ІG@C ^$"Dq# I'\p!$é~C5&|+֦LTȑ09x;oP"8A dJҒqc q$X6Is$nz4 TӛĦ}A8R[w@>2cL) P$VLV36g@YF29>9^iLR~)@&ġll܇ @%r#$JReLiX2HDH;RU@`@;&KPUA?5OsAa b\ PLhK țNVjlB2#~! *iY,,u ՘nu+$Þ|<&HXZ-GU J U`AEVz]+d}_X5aX.uи֭>VO./,V"*z/O a;p4 AE0N LY]Z0oeC=ps0wP}QQP{p  p ϴ ! 0   P  p PpS4O6,Ơ\,o gp 1/,"qn 1 5,Q¡ BqG+)SU,q1Q0ORl- Q Qtox qaW*傡ԍ 3!}N!y!s mkV)2qLj$r.NA n.G/$!ή  '!m(gN(%('5 C1iEf!*R)ɀ) vިrRϊT@B ϦRmH0rRЌ'/2$s S/aM/S"&*3#/S62;3>4C32J4SVNZ1cV3/Is67sS7_&us7q77{248x,9k99*@H iD˲9g^rfZL:$CAlɰs.s{f g˺Jjq|Lu =Sy3qGdLlNgEӽ4B <TxTQRGS1S=SA5TEuTITMTQ5UUUyjU*$R]{-~h($ȬjL:cLuV&\"LI"*LYu{DH0< ŽFB\ *BX J0P\,.ėЂ=)>I C[^uU;D_+)IZ颞:̕baq3DcՈzŤjO, 7JHVFFV/&^և,JU `(fT[h6iviiikT>jj6kkkkvlɶl#l6mVlնmVw6nvdnߖm6oVoVkKljoWEwp n7q WqWk qq!o%wrr-n17sՖ%l:Ღr8Wq7tuMWutY7m]u6velivwxa[i4`fjurxWsWywwy7no7zzgw%wʂ3z;|vzŗvm7}ϖ}m}~v%{i;dtk}}7}|| X||8yY(w$HgGm 72x:X bn>8AxƄ;8lD 8[H>b]؃ioqx=xuXSY8mXya؈{xsXXoXw8xxE扽؇8x8۸؉X،= JYôX؏ 9o/9+yH1ԒXgxxUY%u{wwVgٖvWחk%b\y=ALsٙךuqWKsB|e_לYUWQY֝9WM{S-'ן!WWWekƠ[١W͗SƢ/vq5Z1W #AڢMZpWzw٣?V7Q:Ci:owm&6gŨGg(}}r6&'|,e 8hW XXtúL ʁ~X!@ T J@ @ Z+n}ZXRF:ƃǭA:l.[![bdM$ՂlⅦHL pN&`v5fJVIQ'ZoCb'p@2bI":WYJ@2/HȊnehc&B[{(Ǿ[Ȫ ^a[c.]iՏG "[ x~Qj1gÕ&NɼMPB 3`D[[]z z^%icc( ( ^#cH1\a dc\m|_ Q JZ x+DH\MÕbij |=@ C@T௱C \MAI/.CVS(CݘFSKD-6BDa i\Ic;;cHcspB B 1I1;<|%ƽpa> R{$eQjUªp P^*5&~Dm*!}PuMQjjjQ"e% pBQJ 0B5+e*V*;ŻQ %[R.FL}"]g*"TJIUFWʗ*~jZ6Cug/hm%u *}◼ɽ"gg ~JCYڀYYZCZ\>x:TT ? S!1( 怩&t8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;ݼ{ <ċ?<̛;=ԫ[=ܻ{>˛?>ۻ?ۿ?`ؑ$" '.`4yQ w1^aAwXa"!$bZ-H#&(c:_cBҧ4)d}YV@!,'''&%$##"#%&''((((((* , , -0555552//122222"!1%%0()/*-/),3()6%';(C(I(K(K(I(F(F'F(E(F(F )F")G%+H'/J*1L+4O,5P-7P/8O29T7;^;d%=c)=c4`??_A@_EA^JB]IEbJGfJIkJLoFOsCSx>_;g9j7n6o3n1j,_*X}(Wy'Wu%Vq#Uk&Qg*Na.NY;NGDL6FK1GJ0IJ0SL2cK8rMAxOI~SR[^bg\lZmZn\paubtcq|dmwflrkbloYhoScnJamD`mAbn;bm5bk2aj0bl0dn0fq2gs3gu1c~3T3B2<222222222222222222222121208/Q0n07<9989<=~?~@1 H*\ȰÇ#JHŋ3jȱǏ Cjɓ(S\ɲ˗0cʜI͛KJ'Ş>"4tѣH*]ʴӧP XR' bPȪuz5xԳhӪ]˶۷p*+ICړخB*:v}h.ǐ#KLer۷`[j+o{"5ѡAo6{5ۗsͻl&Ẩ3nWqm۳m_b/$ե=oӫ_1wgg*ꨤ^$G}iC䣪 $j뭸VhWBdixNj*kF*HlF+Q5Kmfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GHp0@ PY(2 C搂>t(!bD,b r&(H.Z*ZX̢.zp@X0| d' R;`{jbV]eCђN >Ֆ+]wYͭnw  pMr:}tKN7ͮv\rwKv׼Mrǫ/x+v!ͥ}+]/pzH0l`W v%LaVU0 "؃pp &;*crUbXX)<|+Vn ~$6VѓU`9bVFc<Kn|b$C2bH2 'UHG51+[~qd&yw?h+nQMy#ɭ?,*`&*a\}nD>:QUqe.pA) ]Y7rw_yؐntfZWs=k3x\u=o#!ώxУ(Gn|7nG|?xv{o+={e7!=cB-捄lع)p Zwc=/]yc.iWFG6ܔN:kn~,[$:͍\ؕ5ΑJHկt7f.|WTG:Mj C pA\4ď[oO\.)=w2;և+Ъ@Qsj << t0[Ιo4vϼ 9-|Mfkj•=z˞}ORvlOړ_3z̭™ |_-\#϶lyp 0s?/_w6kW~t~fqUyx g8pwl{Gz7g'\(X(xHr iz{G(sH-x'+ȁ { {7{:8\'8?ȂP8v|$UA1^T8gi/h]8)Ȇ)HmEs=(E[؇{.vb XM fpx}Dž'X*z\w#~v u xqS}e-'\XcEtC\Hw<}y38\] ?J\zU8Ռ||ڨ|i(UoƥsnqwV VVWf`_rΦdcxsfvΆ \yVNnh\ &iȊJV/  錻{%W$y'ō:aoyk us6ކ}fjgFk?MylSM=Qw JYj(MUٔl¥^)\]W AnMYjlz'\Ho wVW~6IjkIl=)^]F0 a <,<l쥢ZoQ ,ۧa 3;5L)\AP5l79+=Ȫo}ܽ1[.;{· ʔ`(dFfTR|[;_anjM A`Qq<s b=d]f}hm2#Pnd%lCP =}W * p޼a q g0`] ْP=v 񐢆}bPMm֫p0L}Lp fp  =l Q0}11aQލ }ޢoT<` /Џ @=b bi1 TW½S}X]MEMQaf !>g$'NI,jZr8] H= >Rm ܘ ^ F}, mQN1 -VT&MR?LՀmې<$*F' +a4#1+ Pm:rTލ<#M&JQDJFx^MF &mG(MتKWuC ~!aQ NN g`W.`NJEQ@ԘTߝ V^eg(CC &Z~.-( Am 0_pLr1U-W $,;J $cA0B (ޞ:*|i8Ɉcn qBhd$ FҮ@,̔TLKDQH"S7$/đ0ʡ >Pm'4b(/\aӕ12t2N9N;oڠc< ?φ-t@ =tTH!% ".'r|xZQ2c83Mbh2Xi3Eq@i vIMtC+YF;5\e*rZ"P/ZРn7\q%61T%?l% ra {Qա|ڶcY*&x 3L4i\pZ,3Ա,} dD57fgf5,‹۩zj(2Hm>肌z+Yvɮ㫠3+ۓɠmr[FrGR/UƤ 63hM7&pwp7u*Cۅ3J@Ёb3sIlGn GEY/lQD)C!o]yGv'MG>yg>M$yqCj9g0{?|'|G?}g}߇?~秿~?`8@@6p\\*H<@ ڤ(XAvЃQ AV',a U› o,aYx.7ġ - t9b8D"уmOcDb>ab@~qj) GxE.v}!Av/z'~ ˸F6r/oPF:юwcG>яd 9HBҐDpHF6pd$%9IJVҒd&' IMvғ'9JRҔ)UJU%*c9KZVҕ,oK^r&*U_Ә,1id.әl3hNәѤ!aMcV7NqR%#yjTc,8O\Γ=cO}nnw1xlyH ׷)I s\70ha 3r0A2ty}:!?C50H~2z?n~:?ʺE @>T@=z?+ Y :z@5v"SDA9Q !$"4#D$%d&t'ʁ)*$ +,B).(0+2B.44|B4T6•0B6t9$B9;<"=>C:@tA$D5CIC,CCd.|9@hj2|D?tL M3=PQ$E+4SBȜu}0#8PQ2HUx0`0PTT$\4[%,6$glhF%jDk8F3n|C% Sq8 X!o 4 4)Y{EB{HL,R,pLS|]<|S8SS E\6=D8D:uF?O TNL,GR?9]TB@TMHmL)uT5KENU=,?$TSMtWXY$*hZ]#}`=ja5c"RlsֺgmiujBVǎi)lU0lWhru.rEľ|vy!jWz} [=v ~؁%؂5؃E؄U؅e؆u؇؈؉؊E>| |%6v@PFX ]Eo!;WP75YTh8BYY9Lٞ ZtژEEZdxڂZY6I8YZ/iڮYuhZق0#Ƒc ЇGx!LF}![  ؘ8 6hZ2ziء|AҍP: ݇]55֝׍ E \L K-@?7xFxxu1-\ @ =Ȑ8ށ\uQԡ=1`'q!P`.ߖ`x(|_`e;%_X`9Z6A- .>!z8\ Nb!LN"b#F$a+&-~%"!3a3uM X4[6i6Ȓ-ٔUi9 f5Ed\ɠNFF' Iz Ƹd`dA"%d!JeKΠ$eGMNSUWvHY&!L<'IpNC&}7a :v ސR vE[g]XgmY]q&8` hg6~g9!ކ{avB$Yh qYP\m a(1rnZyȜ(8i]ii= N Fi0ʖ1i&&ꖡp ^(- \ӵ|@`jL(xfv<ٺiyކ]i܈֛] R80l6)l !a!Zvvy89lY1uvYlNԖl~&>DЛvg~€϶XXynn{&6= V(i?. ǀ`JZ0UZoofJ!ipb9f7 p^7U$Wgw߆=iGv@vxXXX>b_DŽ[Hhh؃Wo{W{o{7O{{{/wG|={|| I/o|'Ç|ʿ|#(񈧎u>ח>(|_7MgWyPxwyyr7w/x)y_ϡcw՟DŽjX7qt+7U7w~:oLhp`A .Lx!Ĉ'R!Aj<#GG,dH !\y$̘;(%Ɛ'e'Р%فGXP1I:U(ֈ*%2RBYǖU,{ȲmKҦXpƅ{Om6]YM^‚}zX/]3>\8'͚qqRsEv۶S*ݻuO~Q+W {6ʛg\w‡/>vqȓ_\ҧn7)ڷs;Ǔ/o<׳o=ӯo>? 8 x * : Jt-j!z!<9PK8,!"-}#v8#9#= VY5hEy$Fʗ$M.$J%x%H V_2HYY&2 %q9'uIpڹ'}t TCz(*(:(J:)Zz)j)z)Z*&**jz+Z물;, [,,ͺz2 K[RؚJ!h;nޖ k.8/n΋on0ICybC=lSO=kh<*ũ?RN|hCȫ|rL*?j[8pLL(3B.IO o#2nHeV|׿j G?Tmxxt2պ@5ÁK~o; CS͡s8H5;x> }U3 J@w\=8ST`JE 2d0{X1Ѐ88c7bTUMG=GEWRh$d YG돧j$H2}%G3>'Z&69PT&KUa8K"]Cʆ,ײu1B-~GIU1 rYS 3R5SUG695nlxd*ir \bN7JkX$IJ|S<[(T&:?vR,86 ڳ"tb eh69hh&IgC"DypiLEpȝ=M{mqJ=@20+h`@jW`*īdYZ֯beF2Y1ְzekX׵JcNG9Ui«_־6]`zD6ͩI]KUǎT,e1kZV{{C QԮ%)kZܪͨTRq@ݮmo;ĕmնjR+oH{z{k^vm.tջEzv5U\*M-|^r-FbBиc|=iR$Дd'8RXĥ"qMSeYBjrqaX%&)Jkl*@J#?TZeoҗN}\}TgfIƒZ, y"kUAuHrq!qI״54ЇX=mG&4 <HYy#"/Ljќv4@-jR=euG75:85?969N5ՈƵwi_ԃNW{Imh9d4]SQ!vHsy#.햴+n5Yj!y8KGќ#yz"rKGD'4 _U]Q /xamt<s+oyVs  Gs%c<7יw "#@=sS)א?Ы\c4M>uUisE;j0wo8׽Nr#.A) vkW]2x Xf|xȯ Δ/*n]0A̋kO?w8yJq_z݋灯y v}㑟|ry黌Ds>/~TP !j?7> Br &ПQ. >V,ȃfn`~ z ` `5 `B l@/W/l ޠ{|  a{a|12.!aF f{ -` j!> B`!,&YXH!!y̡ "! `!&"."q"2"$F$#B$^"&f"IT" ~"((z")"*(*+,")"-".j-"/."0/#10#2 '1&3`3>44V#3Z#6b6ncr7F7c4C8"8/Vbat@. ``:#/֣=">/4?"@03:l> 4~:A*``AlA@AI̟8@d$2$LLL֤I$0ޤNvbOޣC: C#AZ=$9n`t4AV^%?O IJWޢW%ZRc["[K+ƥ\ 惑`dPARr ĀĀH:pPe>eO&j܂;%]ceffeg~gI`B\ErjQ:@#c`n`f)B*T?fI v=:&`*'6g/J@'uuFALvVg,:.wx^uv'vvy wgf'u$xByb|RzbB{x"vgzg|'>}&~2'zzܧ*(yx:{gN h|~'g.(j(K&@6'hh,Xhek&_jf`m:@ &reRhhh(~((yhXN&Bvebf霦"pd$^FeP>Φ~W*Mjr()Ljpz*=084B#kbf` hbVjc*4ۛY kYA+A"H*(*2OB5:@B: C"h6iAl0f|AH.bIDk`B㻺kk#~Nkfk,ªcÞZI l"lk~ZfZlrJĒ`fcbJƦl5?,Ϋ&,֬,,[ء,`-ҾaѶa6* >-V-$ Zndr-؆mٞ-@!ڶ۾-ƭ-֭---ncc ͪ:I;ADD>EDh$G)`|B 6^dnw)>BXwX@?>D@,PpF" b nbB?&B` aBpp 'eD0&0&0wٟ 10L00 ǀ DP0{A`nv0&tpDlVC0O/$)&RFC|@w\F 1kj6tA1&T@21{°ge#gPAܪF&S#C2Ʀ%ߓb%ۦ?V!*{V:|@>򛔲t$H/2&Z1 C0D D kp2@0 26v.qwLCl!D=f013=`/&*B2q7wGӳ= 5p??ת?/o8w(@@<0D`B@ |;2m`Jt;&gG[=k/Go?g@JDXtKC5:42?ă{gs 7A@sxjsS8w49 p /9۸q*8 rcAP-#eGD'3=xu 9c0C9h葀390>+ ynfYJ5b÷q9D,C2(j={3/XkzH9׺}A+)rG'wr$;@벉HzqRtn?XD&9TAso#6\-2z6z ywׯSD;3CDJ@-D7 Dut{=gB?4;{Ⱥ#wxTǻW@׳GXG5PocX;Oo|7&Cq 7k L>!`4!<_>b !v>g!xvɇwF>Ǿ>׾>~t  QC^3#^_HI]ٿL=-ZwN0Q[lͫͷ J:ΪWfUkW8~;`XgF{y##8Ȑ)hw^Sղ(%ʥː/^]nS8~d|7oSbp9EǟyCRڳͶ4n`_[xƑk$\x9#D#)&\avCSaArǑwc~i{7>>l0[nelAju, 5$Ŋ 蟷3H=&N)hA̠[l UP!Ah)FGc!dο~e6*l˃d$i! 9c3bL`zJ84hNO5(O:O>4,C,?LtG tQDTKMSJ%T;?-UQ@35RTUuPSWTiSX!uV[R]s 6U6DOeTv31HI-XwM;(ak(R)aG*m*l^pʩ!GF =D!E4D%.MtE)NUE-n]F1e4јF5ъEEf6ΑuG=}`ZTb 5\QXխ#elce ٘V|@wpYF=YV%mi-ZBp&qWhC@-@DƳoWvgMlt#}l0iխ8 =|{ߦ-o{WSZ`-bVڈ4-`r?nҐt$/9PIӮlD#a<1(C0kw'Gb;LW K}q% :\|$Q2bnPv틽sNԸwb;Ԧ6|bT 205&Զ!:˓x0|B',Ё,5fp z a#'` ߡʮRP'*dp"* x $?0 a  p p)BFZ"` 1l'0T'|"'*ʀ u_c° z"*°'*.Qp{  x"#1*{"v5bqǠ *Ԩ!_c[ybŠ" hfly^cඁ }}B*x"2 &Llk+Bٱ'\' '*! ''܋^!91#}h 1{"Ⱥ''H' {!K0!xR&;i'{'pKyk$&1&b!R* ` a%}%U+ $ )7RH+aR)$H%'>GMd? JX- +'u x˽/(VR@@'0[32P@I*@2R213yb3kZ-` >\sA!02i )?)@(C(E'G'H'I&J&K&J(H(G(F(F(E(E(E(E(E(E *E(/@?=3LE-RJ+KL,GL/GL2GJ7@A@3;K,8N,7P-8P,8Q1>eECfMIaQK[SMUTSVXXXZYY[[R^c=cm0hr+ny:w<=9:>CM{cfodrdoEzAINQփʻŽA@?>????=H*\ȰÇ#JHŋ3jȱǏ CrMɓ(S\ɲ˗0cʜI͛8s,YRϟ@ JѣH*]'ӧPJJիX ڲgO[Kٳhv5i[_t*wSx˷ߘ^W֭8Xd؂q J+\JLd_\02apnLc9]Ͷ4װc˞M #/4oԢJq7c{r^7УKN2_.Xaj'w^r4s]m޺qyBr ^BÍVp{% C^XevhQ=ePv[נ)(Qx+ƈg zX@7HPG5\]٥6݁Qؑ\b$cdi&Zm)I⁎$!ion%e٩"jW$B:c}&! e6&癐F*C]Jrd⩧uiВ bރw^j뭸T%|&9g[#vccLn 颻*VQAh{߹s"$ivרF+Z# fjZ\_kCZpGxvyݔZ:Wқw\߾ p~V}Ʈ,l{t!y/<-D_4XGL7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:xx> IBL" E:򑐌#)JZ򒋤$&7Mj e&EIRL%)QVre&HX򖎴%.wyH]Π/ _$.Le.әdd4GcRּf)!rk& y@!F7?  YsӐɈ>QHyҳ)O7?2:H j<m;9z1$YhCvɬENws(/@ $>̙?hFaQ*7-fRͥNe#A sR(0Qj &#İ,5gu~t,/ ;S:,/Lr(h(ZҦ#k ?x^F%T2גmFghT`cZR"[Q >ȍ\LDHAd{۞eb _ceKЪ}|/;_ĈP lUA* }KI.Fanx~+Hœ\-]Afţ4!rޯE/\q}o >#5llUoJxcml&T˹-9 f*֫diڍ-GTFfAX>sH@ЈN9a|JFŎ%9kas .sw s(ȼ8oz9|$C'Շje%k k1CϵȭHk[׽=깖;=LY8Eri{`׳5mkW[q>6W~mn6nl6lF>w= {p^o7:^7nۍp< 6rDxC팇$$Mm\8K#\2E~r3΁>t+~D[]Qz\ycn?h rLb?BW.n]hD:Ŏ[^6kOޯ|lۓz?`qif.?pNm ޏ{7|W9yW>t"Y+C]yٚ![]65^zxo;Gdj_+X'˾N&2-0S$vy'.O{ _QSU6ꫯc1~hS[cZ.s5VGwhͶZuN] $]۰OTtRvG (kЀް Hex%Q VW>SGHf&ed~dQ0hN xn+whd0('UxgIGIxI WVuwNfHcH兒K$qGdHciȆ1$Dԅ^KȇIG'Gg0^\ƈw舍Ggɥ($eHh$mGi c @c0Exp`c` ذEc HEph Aؘڸٸ;ьh@明Ȏ瘎;8EɸȌWc` ِyB0Yy<ّ;8q1eUXP @ I* ݀ ( 8:P c'1Ԡ y Pu3)-Г-1ȋh)Z.i)&  &p=:XtYXQxw||J P aI d9qi lY45Hc(븙8WH XaəYyE#i0j !`g 8()S ^ 99`:Zhŋ8)雹yYٙ*`)m:X ]iA贞.i`I$іIBY JC :C*Cꑹ "J:&z(*%,ڢ.ʋ/2:+:6zz:0ʣ>*5BjA:FjG*ʤNERjSZVʤXG\:^?b:acTZRJpr:6Zv y{x3u@5yب<٧xڧ-Ax (^o:cʩB*J꣠: Z :@ ug@ ܈cPlj:Iۨ=fwʬ1 ΚȪ ̀а `8cګJ؈֐ʢzZ:ڪc`t ڭ9{̪Mqx00ʚ;p[ q{h+% Kɚ;{1{$+&(Kk+.˲37XF[HK=>۲D+CGMMN۵LQ+d U[ȵZ+X_o yc^qxyۣPpR٪k؈طP۶2;6*뷘w<+kh˶kfO۹KD(ʰʻ껾 گךxI۸ikj ijpKۛۤ˽{K|ژ *&k;搟ѩ ܗl, lƛ*pؘ \ |!,#܍%l' !|“p  0L4*58|'_*|8 Fd^f~h.Bj;jU%t܍n>IuˍC>|y҈;` h`̍ ڱZguvNktK5ee Z3$װ>;+mz>{͗=ЎAmNA^?\.6p  A |u!1hkzx]q<džzmY&p4Ȋȉ؀.Կ  - *˼3MԾL^u/`$y\d*ik^um|A# ~ ۀnORYNY> ip9LڰNٙ@QwoRy-蛽ڳ  M]Ё $ɌP T?*O]A c_m!!pƍ?D DPB >QA4芍1ef 2Qi vLIEA-f21QL5męSN=}TPER9F!Mo"5d3jLP%KP(bN. xqvE$ʔ)aCpuWoA<3up?„10m;! <mpx5hq;厘Ƽqrkt#֊' M,H 6qt"7moc 6xz)9Esؤ3JR0\!JD2tۖAŷR.3rGH,?Xi%-f 5rme0RDP"aڊAl#m4e8IX E9ϙʹ tgAINs3=O3'>O{sg;J|Ԟe@!-.mE|bTC7O5@9*ё~T) :)zђ/K;Ҋ41i2=>іca6hNeUn+'{r\STU-+ZO՚J5duYVRq?>},FXꉦJ3dMzֻ5٤ bg,3[͢&dn鉤md[%R !lA8<0jJc /HQT*N<.1kr.mSgHT ֐]xB*"N#3<r0 T>aڜ|F&Tu$b8.6"!'cѸsv ڸ>Ne~ 6Fv!*9NN%dNʉ2qٷW&n_ WrcrѼf0l36g:ws y"5 +JiKWhFCdgsG _ G" i2:W`#FժԡU֥6֥d sg[_l:Lֻ>u iTi8ٗL xG.K#^fvf_W[^}yWC4lt..md*烓BxlI<M(?\C("6Mx GMrw"/kEO$۠ 4ApyMv^ujm%6t=(3C,Ӊ~~t,^z-2O,^8GWw{5М]|?xG|x7|%*qxGA1w]_v^АR@FdG 7aᐘ7sC)aRwڡiOR g˴+b Kpζ2c`Ȅ?n}:hRAloaE!&DA`lBÿ];5+ FYFf Km(7n 93kHk0@ٸkA8sFi<QhR? ɄmȄsx($‡m:,=`ƒІ?p?!Av)01!=\逊@f B3 ){R%1SnfS1BFEmG@ei|TxtITSKԬTLm(OFhBh|hH=˄np%T XU[}0؛8U\ZUa]Uc dP=sVVY-V;\e]XM|BǘւBQ=h1g8fP!IDӆ <kxt8q>VV @>=XC:`>hp>P|B‹iXkM4;Y@'|ÏّM؎؏>قrEyTh8مhg(gPגٰ886iX8Tm+X`'(ZP[EPٸ}hד ,[B ܻ卶mmUBܼ]ZQ=ɍM[ME;Bi(Z}@qV-u@GTݵ=VECH]tC惍]ֽUލTY^ܭֵ=4[,܍üT^܈W=F :0,_ `_ qqA6ŌnY5A A>ŘA3 6AD=|` iaJa7a=#ƨۄ_*ڃpmMÀ\`h]BE+Nٕ-/cb0c.f8c4e}Cc r\ 7^]鋅} ]9l xZ8,ؕߕeܔKmM>cm abK UZNWeY&YZ[neeY8ecBM%]eW \nl:]T-MxXǨ ЍUUeU5lDU YDu v.Wp=zXz炠Do Vgt&u&XHӅ-hagFfz6 & 0 $ii&6.d[<, j47[1stj둛: j&qVkj3736^~&4ӵ !!f̸4kiVL3ê;{BS8ŶlGy:ylۺ(nk;VVv׆ؖ٦ڶstVvwJ3'ibdf٢e^X;?:Æ&opr3_3]|tj [} @.>Ppdpbyg:`C aa^o'.WFs dB3'04G[_^qe抰C>7'r~ VV"rP(*+,-.Vks(/ ij!TC;j/=Ls9Nsыs@L>s?1m>G&B76D<6j{kTTKBZt)k찒Mu&u3͞VHOl:tD';;ITu];uAg5b?Mu_! 8NcE8Q(lupn_oo_(uycuT2}w%uwltw~Ovpu~ GwpQppZwalLgPo;;ovG?Ŝ4Ks:Gysv;[?E<9sxu/[Oy+kv/1dXtQ-zA9zѠvu;@:U\3ZE/D{c9V{.vz8N|4wLJȗɧϏ̗278|χ2ί!21A9}c}w1Kק$J$q!Aygu!jn0&[/JB~ZZ~i~zn`-3 ~g~燠_ ﲴ<߇"x"OWs6'ݺȢ7obA#Ȑ"zhBjZ4+}>J8[ƌGaɘX@Sfc͝~TV6r}kǒ`ǒ-kVXS҆R RNNBԳz!z/׮ $F1bhJcF0KO={+Рؙ"RNhkXi[\y f; ,EӺ=v@כcS<'۞pnW@7^3幝7+)w,"r̹wg[adO:>m#eo{+'k=@м~52YyjVU5؆:Z YЁ .x܃zV`-`]S@VRQ3[6,ܶ6)"Y8ю<я9$HLؔ#N]V5LUڥ~TR:`ps{a 7-k'BlmV{mH- :)f4.ǚIf r^vG8B QsXolBK~'lFPvz+pȦ)u${js،52Bqyo旓72_rt Ĵ_JCݗHGTgr2?tTCe}X:?9q@KɲEzvigmlzR鳥> N]9Ms *vcOy~kos-@׉.,lW?0X(V=T{AdٹT?9i 4Xe!%z${`IF&`(Nt4mo!T0Ȓ>$a0`ҷ}q|"{^0{ t03,&>1S V W8n1*1= >1,R~_h%y,)SVy_\d850< wQ3Ӭ5ר<8tL9߹m~`ޡ2І>4 2sdEwt)MsӞ6,PK˙y[KF5c-Y7ua|\Ӻ׾5kG >6e3~6-iS־6ms6-eKcwva4%~60tӻ~Q1i/u+:C.R7!&_p layCv؁!ьU 9w`Zp}A nnO<1@.}F#HyLd@Q0{KqvYf?;~ngv˽_/#: }94ΣB-*<4Nfꁑ:շ7l0A:sP/c{ pFoӧG}U֯/=I|{=b{>"H"" y#fWP1H]ޙ؆[ od!FN!V^!fnay!]!!~ٱ!!!aݡ! ""aqya"6#FbA$VbZ"&Nb&nq'R'&"(!")a`;ϝ"&,!-b".֡./b06"01"Na53[24"2V^#6*66vc}#8Ɲ8&c.c]4#:;%##=ڣ<;#:94יC#=!@BvcCnCbcDVDdB E*am:Gbم$I$$8dJJd$HHIʛIKKOdLLeM 40 %RTVeRNePjM.%NB%dS$Jn%VRU%P%QeZXH%OeVbUY%ZW`FeYfXe[v]%W&RWF\f]N]&e^%g&d2f_*S%^j_&i[&fa^feh&`.c6&nbFogpp>bqq*br*rasa)9țōts2bvjv]E&wcxxcyy@"ƙ vv'9g}9ڥ'.g-&.b8~h'"(B*b⃪.(}wNh|"h~"J#n('v:焖,(~h'(2hj(r(t1) ;C>iB?#ſ%qʗZ)ii " 4h3\`A304``)áFhC-C2X]Eʭ\˽\\_\_hܜ:*54*ʩz-CΥ4Y433i\ lj)*+z}`N ``}u ܲE_zՁvӭ>J_+YXkjԝƗ+)޽k[>k1Gk C+(qmE\|mq_v_z^ɞ,ɢ_^eف4,,h ߁k>kf_RaV+b6\mZr>,9--~Y+#|v `kam2݂mՊmߎ ݎ*`PR6 jÂɛ> ݎ.|Pi`_~_`}.QTi_`z34.$N*6/!iAN/V^/fn/v~//Vob! ;^!` +&ʞo/r\0V[./rNZ2E+_z`Ӗ_7x.%i^_i_xi.)_-.{@! =*R\ߖ6۾2J:4GN^r2jB_b \2S.]`_kzsbjӉ@Ol*B7]C+^]֭i1åE^z^A_ij`9Kؿ2L/^Mct+Y6Xu2 I_Ͼ,Ȟ2[ҾB__Vެ٥, OѝDz,YZl_ѱ26XC)ߩ&Es_0CپmN{5ށs?kފc֢zbvdBkNqFA^}b3h`&nk7KvQ6`"n6-.*hm`= BHt<+>࠲ꠞ?n5G6șr JJ.)2^y+?*1#/"bx~緹788'/878{鱔Axd^Kp{ox㵄wxw#9A3Z188K8xy88#ٵxi{K8|59yG9~mk!orzgyc[7ߗA yqHd^|9N:Ǧ 󹌳3Oz{S[3װ9C8߸Wkcy[9ùgq1kxp{ۊxx7;W_;go;g _{;blֹVֺVcVq};זϻԻ{ih=V^;q9)]8 VoVûӻŹ4PH0 Uh|]ݓ`[sΠ 왴VBNSV)O6-UOH6\tj L& sg1V!k. Vukׯavvm۷kƽwغ}p#W[IK o]m cΜP4.F5,Ma{Vm7ݺFJ홿SBlD#,4m!)lm 14 9<3:Dmp5羣(j"O:O"QEff*WYf5jjiĭ*eMHq<;S+5pG 0d2<\Qj[ 1FGOLsO7ᔓΧK3/?5r2D!g'HSJCSO3O)U3"ͰT[\};YǤV[ͬsq-^e35"Tb%DMe;Yל}9iԬVaUoVq˖\sE\uem "\_]m7_6^-NXn!X)1X9AYI6uKNYYnayǐ+y~x uF:Ύh鞡Zꩩ^nh=kZi6[)6<0θziFZof\p&l.|l;q)om1f8qQO]Y|r]W=lrSkމ%/~yO^/8rk}tfϞqv\ /yhs~ 8a}vᗿh~?@2n_~p:PX?e5Εocv7<-V=-Nws˸ɐz= 6(! WCn@tرw)a_$6|dz!c&wkH9G*quX2s?LGQ4!FHDMt#!IIN%1IMn2'AJN4)UJUt+aK-Q'U@-W8c /LAq41:HL! @A!*o|S0UIR9χA5C@ ^vlA  7 C/NQr&s;!Q4́@c 21L3 K&QVF @CMS3g| |z4igPmpfo=|4_W??ԗC^oI=!Sbkg4B3,5PP T ^fɞNK  kTp W*")+"$z Րa Р 0"N=N Za@d)P JgB;#ZAA ! cS7AK& t&3jqJ3"tBC=`\4a3At4*e)s kJIϰJ#wKAKL.6gPLTJ=CI0"A%AHt4"+N"O*%4"ܔ#csNj4D'RmRGSS1SA5TEuTITMTQ5UUuUYU]Ua5f)V{ɶ"h5W!fPzGWЮfЁ5YUq,Ր3ZdufRZiZOXu\aWȏr[ed:r<]ە\M9E9Fw h= xhxL4K46uPyzMg7{w{{{7|w|weW7uuniZzJtWȎ~cbh~ xڨ}W\gxՀf br`1a\(5aY rgtW/Nb8bb]}X#]a؆db][Ux=$؎Rm-vYexln"؋0zw1odz~ucqvp8j҆qXSFddssYc؏Kxat؊wxr _m_mJp(-5,hu{zs^⋺]ZKᡝc]ͼ~]1U[oZ{Zy_cɽܻi>M3^~γQkeҙ՗<ެn= |J=Z~nZ]^+w_W:[so)=u:׍|+1_?<>?Ak?K?=qY߶3;goq:}]~-u엺mY}WqA_+zUݧ߾zA• >|#`T,TJ ZE H#Ш" XK2ٱF&SlDeʕ2Q<9%N0$tPaB'R$JQFx\҆MΗ5nr%†IWalHTbFJn1cԩ^ΰ{svS* -6ԷNRKRZX>"Qʕd /֫9pٴk۾M;en ;n u 14Eg)|) z}zx^ ?x_QdiHNN إ*]%A>aBB+Ha`@5dԆ]CHUJG5]*btA~Hv&.ȉBT@Hd?yhA4f#蔏HbZRӒe9f)6D%W.5n6%Jyؓ;f&AfG hgsQjdFYv68Vi&4:(Eѕ R bXƘb|Q^H*Y:_JAF*,+B,x`*xk!i+㳏EKд^[PvmBk:n梈.a-]*Sɛ +Zs2.;ѥo* ۋ UK J6Amxm2]gM#yK+^ٻNT ?f1wHQR/J-p{ Zٖv.dU[\{l Zzڴ56olh3;^nMWl6vpo]n^kܩ^mZkkmU=oצ[uSoy.,q8Wt[EpCѮn+"bB`S|ƴ{-P~hoI_#RMn^gԹAE^r ^Mw1'xQrϩiWӵjzG~szd{nbg]['r\OK9.qO{8͜^bC3G.v?-/k~?Ћ~/Oԫ~o_~o~ /+oKԯk/Ϗo(Hh Ȁ (Hhȁ!(#H%h')+Ȃ-/1(3H5h79;ȃ=?A(CHEhGIKȄMOQ(SHUhWY[ȅ]_+XcHahggPBp0 ސuhw{c0 tHz@ dXh舏yPc  `8ȉHH d(Ȋ (hh 0ހ!,        ! $ % & & % % $ # ""$'''&%&''''''''')+-. / 1 355665530,)')+.0111111122 0##.%&+)+,,0,-0-././,/+)2)'4'%6%%8%%:%&<#&?"'@"'@!'A 'C'D'E(F(G(G)F(F(I'L'L'L'I'G'F'F(F(F(F(F(F,H5T<[W':T*7Q+6O-5J.4G-5E25@779>:1BA*DF(EI)FJ+IG+SG+YK+YP+US*RV)Na+F.50212122222222E5<@A@?@@AEZ}컡IJýu[QQQRQOJDAFKYysldt`n_q`qcmrdRIE><<:8|=v>q~:kv5it0hs+hr$eo&al1\d;]^F_YIaXNaVX^ZY[YYYYYYYYWWXRPTQPRNLQKLNGJS9J],Kh(Sm(Wr(Zt(\z+d0h3o4q3p3o0o+n' H*\ȰÇ#JHŋ3jȱǏ C*q(S\ɲ˗0cʜI͛8sOOIѣH*]ʴӧP Њ>fʵׯ`ÊKI[XuR[C"<{ہv˷߿ZpQbju W%˘3k̹ʹq*(bċJcBe-dԞsͻv }WI++ IANL"F:T!#IJZ򒕜$&7NFR (C QD*WRt%,g)JY|#m^R f#)b"Lf b2|&4{)i޲D%DFAʀ-d Hlf<*өNm|()OR@ ؄>/GPd=!{  AYN:ӡHjA2|3 Q_ҳ(H39R\<)J9B#/Pqd#vX@ȜBT+mePO 0(GNN-$TjHӪlKK o02M4 B:UuC-)HbѨ*VW՞[:Xju.]7n*DPO+Xf!"`.8+F ] -iEKZ]mj];Ĵm[V]mp{; leײ6̅s \`%o܆"WUnsk\wok]W}z{φ7]s{_nv+zt_8}0|'L/y /E ҥԧ>1!Slx^-cKҧ0y[a+,a"x;IP/ca >nrBWDu i7YXygjzvՑE>xJx\3JV]U8G d` [y̿DMRF'ѩtJ,'TCs!PA΂1[%=iR!D*g yi;:ɚz5>UOGƉOֺ"k΢Tˆ$Hf'ڄv-UngƵmmDw;qs;foh-TzDx2nL?'Nq\` u [T\%%6 lHA ="Qb!-@a{l={c@h}!ё^ox`aNP  Olp 7 $V ! `C`9dc5N> zMST @a8$y`@z 2/o@P>)|AK,;5opC `yAT/{C pT=$?ȯ> e> x 1εO Ք7/H'VcϗH $_aF^BwX}~QPח}ygx8 fBGOQW~q԰ 8 82@sN W)Fr'XR0HlQ3Vctt%p}tOh=EQ4PH4\ )7H?b-b^uIhcV)c4bp^`P`Q!0P/G8s&}!g}L}7'HzxL׀1'H~5H5~h|@{&']U 8~ ft}='Hw~ Ƌ{O0{ Q xr8 8 0js_!H7 :r+w @ fG!lzLvss)tf?sHw yt/b g7lhl1O@{A XvzwH Hs?wt+94Y6y8>70 :'"H-(]H.md%2H(HH?i|$$B=f~ZY;@N^HOnHuOcՕIT#HbO!XQe55먗tS  ɘ☑3h8N٘%Ђq8FWKB>ٚ*B9yJԒ\#H 0WQ/O # @u2G80 IQ!w 81$g)r-Pʙv2(8s<A7tE't)R E@xzV'yjw Yww}{7zwqzLwx'yy& Y#Ȉׅ/}$WGhWHX& Mbp Az# ?p'3=6*xH!Ȣ18 8` ` )i}g'XX{z"Qpwwp(VyifEc6Vl?a=Q%DheʨuVtVfPXRHyU8H[X G{. q2 z|VHX5~X(Ht՚$rW[ xO ؐ*j(jyHZ|W*8`](~g8H(P*tO*%Y::y k;hA1ysyi y⣛LI:<۳>@+8BwXJ1dJZyjRer QG8^+YFuٕQYUKl{n]qQĶL{OՐqes_۶rۇXH뷮Դ;K1:PgZ5Z;NṶQ+p]˹ _+zpR˴kkg3Z \iЅKe«ZqVVSu_faK}۸۽ KaQ|ۚN۷~p۶rKE)w˷ܛ۸+pVU &K; UbUj5Sa˽Ak$\&|< *,“02<4\6|8,/<>=@H-҆ bl֬w^c>΍Ώ͑~*L -* @ {mFNͧg~3L >m٢ ,碞ՙ]Ȣ`| Ȋ?(OqU.2_(?}n>U FN @o\ BI;S/X[0[`\M{o᫴[Lml"G%)qhjJ![o+k"a/@JO+ +K7;wM{$N5Λ_+oZ`GK;?SمY?vP/؛?[؟1h @!Ϻ?;_1DI 8„ QD-^ĘQFUkC IJ4pI*;SL5męSN=}T(Д')\ЛK*%ԒFiJ]ZV]~VXe͞EVZmtTj\uśW^}X`… FXbƍ?Ydʕ-_ƜYfΝ=ZhҥMFBTc!`-mƝmʍp*]KaRǵE$baA0}S+Z͟G܊XnEPK>IJ6o"^f&I찣{Q0Abv)A /7 Kj(f(BH4 BIq3,K[dQ2МjoB q"&oo\cq"a< }|2v(vD3M5AW|Eڄ39݄SΈ:3"O\c"czPrMl $[45TQ"" /U,I-T"V# GIQ^%دT .t4ӵ ƛjhV`p}jGnN >e]wc*Ŋ3 $ZIFNwfayem'b/8c7c?9dG&5^LF9eWfez9fg9ުj9gO޹gighizi~:;ZiVj.kSk{k˾lV{mviFAgv:E[l6o| 7gyqqvq%G"jig:6)tE:r[6UW:uQvueW6=Ή! ?__(<4ǛϟY{|ߤ|'?g]j!!ܷ<|h$mu@pQ @BN|`A oN"KxBfЄ# _GB)| G8D!e8CP8A& "KC Ұ"D"[xn~'0pS4D.&hlD6[#G>vic>*QEFɌc #$H51#G?zcY'2x*iJrjp?Qd%r] aRH2w7Q蓰4Q'PRsu6Mqތղnsv :yuf<6qnS;i}3On$h8Pz.ԡ}(9MVgD3QṃiHE }9jZ3RL3DBlOq M:ٖ03 E 8M!,8ts#4D 5'I0U`=xjT)SZv!&|5R*ZԻhu{\sc;kc.Bq#Z7tعPDcLIӟ,baRE THay)W@d?-H5h@ĭrg@=l'ABdi""We?mm%r\v/uoI,"u>j:&5+op )io[#|KzvޭHh[Ⱥ媔+w"nV " N0D`\IXx鏀_u׉l"6Raa8E+0"a1|4w0*hq"L`+=q[ xI-D< DMDD尒'Ů=$ciq YS2~Ƽc33OΪ g S)TlˆX#;I 5g OOZՃC*#gN 甈ӎSBj &ATzRGrl DEfI\iL&u]l݃j:%`pzlY'F"`=&9fү?$73ț-(t@"Iv $9p ԟdID OiAz(A $#DBh$2=$"#AAQ$+̉$X 䯏) ,24ëx4Cs13:| 9>C?A$B4CDDTEdFtGHDl?II+ăRzU.(f>d]1üXi0XhFxF^/0?Z-/\XHG3jBs|2"uTslrFq5dE۷k)ɗ˨ĘɚɛɜɝɞɟJE D(J8ʠdGUdP}Gˑ< <4 <,{!$M{̓26lDM% EL֬5MdQ"-Ry e $O#LM OtK*B  KѸQR=K6m|SzMi+:Uӳak̵`8؆ll[`}O--rTƅ\iz}`~]^SMnaaZUNww`Rdz_`*bPNb.v)*>23N/Vom(Sc:c;~Zq[76+^_3:_]cj7kU?nd8`GA.1TEcLhhaiTr^g5WeP5eNb(fb'w%R%dUOcc~`^@\Q-fR=\jX,&eg&["jVꪞi6ffhvFIjvj6f>k&f=iEgzoԇvkkOVE=s[ffkl.llnɞ_{]kUlͶFmVعif~hNTVF>{dO譆hhee8in]>mkm՝ΗmkFQnnʮVah`eZTd6d jO=~NbCk(FL0d p8&R=p4 wÎpRpR4q r WpeE T:dwlqd$TiaUenW^֒6i\}dYeOm_>'f`vUYjpۅ}Y9|0񮍇K]=fs}D&[96}}`q4w[>ojd tCDNU7T8J?D_p~N_GtN-PwgF7>g9@~%u5E݅c]Zdnhh*'is=_wXIu]a^fM؅5LK_x t8ȇ0c•r=t_wiw[w`8:pgqrtt}߄~Nxw^LfxwguZx[7wߌهxwwPuxwӵ@yy ucOԛ_\Ʌȝʽ\gv6[x.jMUrWUlUuV?VWGկoU{U{?i{rջ'=O|_|Y5|{T}şįÿ|||o||}dzW|O}_}o}}ԐzJfgWdSmxG .ܘw ?o~WR2?PN)[0OɘWtd „ 2l!Ĉ'RhqR> (юŐ"G,i$ʔ*Wl%̘2gҬi&Ž ;,ӧB (R2lѤRRj*֬Zr5SC%j+Zd`lzдrҭk.޼zߠ<0Ċ3n1Ń#Sl2̚7s3ТG.m4ԪWn5زgӮm6ܺwkG<I:DMNlҧS{ۥMذI1"o {`ư0a 5K<9ֹEj3,? S65M"E6ՌCBv7vtO=j8KS; x")V$E5 a M! .$|xυ*4J4quQr$K:$L>%VeB#]zc(87٨:c c>Ij9R؇F:. DCX"hcj):ʨvҗ7%#6)O/ƘjN8v1Q=h7jEmd:k{,AfN"+ 1>hPDK5s0S"{i?γBdl4({0 ?v8\c&T{Aʰ{<*1!p|2)2-21<35|Ca3=s:=4E,I+OL;4HC=5ՅI]5P_5Jo5C6PRsi6=6:=wty7{ k=q2t$3#'qxՏC>?]LcלEg3ttzӤs=<^;ڸGS̒_3?K;WSDdO2==ok/~{NQTܘJ=?OL Ex.Qߧ(v<`@ 6PaY0T,A #zpY EB $` u@^04a _ ?"FrP=eCQ o-Ї\hC(baqF&zщlEE)VYLc8F^1sG$1"݈H>2d<#)<,';MŽ$@:Q3Ŕ *}FJ2s-'Kq/綽t$O#%c6et2Y:iNY5Nu7$q&V39 =uRg&;7yꍝ4'=On:ΞD'Aж-m UCgP*ElA/6.8o&ґ&=iI/! jJk g@c̉)N O84T:@`2ddyS" 8H,> P$$BT5 5`t jH!ИO/*լHF0Q g$Űr*H㨓&zXjL@4XeDu01 z'bY!YXBYX¨I@]Hd ɾ 0Aٽl$,ryS؃Lcىa'hH6!ŀ2ų$>Ժ .k; N" Evk]!E `aw75N"U{A -ޝwCڛN0/jXaHm7Q]$xÓpSQ`/ctƗe[h|=3;SxXLҊɡ-<&B1U,dg9H21~|0yE'Q!^řb$C|cgRt rnjA7!&t{՛`+3\h՚0x3__K+ Ae( 4 Rv y[a/bonoaKVxMif=%0dU!ʀjB<Ȫ4B+s>ȷ1\uuHPaD &$f{J큧tˆ 38#.S83sӞLQ&?9J$*,F&S6gX\)>o~,<ŢBѣ"& UFXi3ґ#Zf`آ 98eH  !Jx&=J"+A-ag߸Q"0+s[kIIA.vΓSճ=c/ӾMnoØ1H;X\ ;]';Qwsd8Oz5  csZdI$b)sc<G0',<>={Y "@JA0D5K\H1ԃ\_@RADȄ,N> |L!lP.HX$` bȏ܋L(VThB `Bd˵` FHH >A|nJ! $ L;s<B CBʆć!ef$Dva< N H ؉$EvJJOHt<=TG\CKOJp F(bOY &</,v"Gb⮔@0ð@4TAC6BT8a`" B`Zn(L!N᳘c5.7n!d hZ`La\ 6<KcNcB@JKnB~7L!>A5D.KmBȊaĬaq BƘ_B:Ie& 6d9nd$D*LA eQNMjL)6IOzV&VfW~%XX%YY%ZZ%h%[6؞%ũCxq^œ%yf^J b؂N]I%f_ydR&bVY(LfuO0&dZ\˅e_bNX&!L.6Dhmnfå}զkl&p ^um:a!&^ĸ8oME ZdgDv& xFu RuJpfq&\n{%k.gqKogeg~Yg{\f}Y,h&(|ҹ%gfbT'%mffTp(`%I'%ˑ表|vƋ(֨(樎N))&.i )>)FBN^)2if)v)nj阖)ivijD(i)>)")***.*\iN*N(D(@(4Nj)꘮**(H5 `­i>G?h ïʪjT8li+A2@+5j66d&Th0C>*E3C39h^櫾N)k+Ω((00op 0sp ðbpװ g0 pڰ˰p '+0Ԓ6mr &k5p6.> 00^h"p11 )6+&6LTC1C )31߰D;BB9h*o­ȳ,bRƒ/).2C2HNDC44-+rJ2Zsbj32޾l28'mz>154F&tV7\,1I@[F7> 540 <C3kf4,G:52u=CRJ5"iZurRR{u55b9uZuºµׂu\˵]u5uW R5a;tbG3a.b'c*d36\(fgvUĨfggT4d|fhwjw݊(vl´C-PcZo&p&:(m&L&n#̸grftmfԧwrnˀ_'lfx[vshp+ucLqB7L $q{gw'z֝W0xz2C8I FLsz$ T HHE`BzIx)(ĸ|Ĩ:B`ccvc;EzB, ;&D"!BV|#D;\4̲%J& &82.L`{ع'-q{L5cHv:";{A`{ < (GaE$`=*El$ rlţ 8ŽhDOG lA-,~ a $ #>>5蟾Q ~>>>AOQ~~#>#5#E Q;?k~"W }/"e{g?a?@C$&(?;<hH^m Ho^Ԑ$t2 đ %dbF1A.uqjբ4 a#EE#cj3H9|g69=#–rǰ} b.z~Y5=*drӊ3,Y!XAj8FGDVG1Yet+as=(eHJg ߈fIkd ՓMWbX/*cInoSl´9̅ Rr3vA< QtwRQ-mMQeZRY~9W!q;|ŽMqEcSq| ЌI,'Klr±QEw]#n'sf)ٜ07pt٧?e{[}B2_-2v f9ltpZ.ڭ;cox{+<ǖ!t" %{%upUȉr]|z1\x Pb;(B7Qy#BQěgwGw͚2#Z{{Zt ?҇~7{K'}ޛz#}iouO!ؗ@ԛ#oe]|WZoi?E|yϾpZF/؏AP5-9C摒X纯Ķ/_+JP$/O?lp_OjOtT֒"y L/Wb .  P"D&AF~( |bJn#PnR,+9D !D 0$s*qs.q5q9=A1Equl LQ'!Y]a1eqiWq1uqsy1kqa1qQq>fQQqa_OQձq q qٱ˱Qq >Pa!Uq "Á N A(rR N!mQ")"15#3"A\$W X(!L 2a$"rg#e&q$M%y'R(Sq))@))q%!&&sr+O'2((2)-r ֲ"$R+wr +(,)Y2-r##C2QQ'E!2 -/s o "Ȳ=DR1G `1Y4SŲ(c LR9MQ %0n  !1O`4_o::S;'@YQ>3s SBaB@C ;9C4g'?!pׁowp Wp wqqq 7r7q:Ug@ `s{uMQ1dMqt!pSWrtS;UsY;;YA_Ca3u'WuwWruusDvstvvm`EIw ty)tpwn{gRQ1ysytw+zyN6}VqwO|Y}w~y}o{QokvkkI~˗~w XzW~w|ٷX|!w rtlQavsLkumM\a5W79]sWOW1_c5B{Uv `ֆ' hdU4 S@S5g8XSSՇ8P8%qS]UYXg]PA9EX{`RGZ][uѐ'5ӖByxZ9%zr tOuAe 8q 0WV6RTykgr_EsEXK75RSyAV?9c5tk9nղ`e3wy}ٕXyQSO7#Tdi,YS h5)7';A,ɵE/63)9yHٞMyM8H"O!!Z:'R*/r>2686P?:K%rM72 Va4ϳo4E7qyb!vO)+Z*Yڥazo_z[1jn:-+zsQP%5Cz bim:z C{zWr0SQ͓3:bJzzO՚FVdfz&Y7Z7;'{ |19eѰ;۳1WGK۴1C[c1[]{Y;ck{[o5s{[aS1;"[[w;[;۳۶[כ[ۼ;{{{'<| \;|!|)\-<5<1|=/E| IEQ|5\Y/>am< u}< ȅljsȑƕ|_ɝa̡;ʝʹĽS\̋<[\}<ټc> e \\|<|gYч\ <χG5};9CG]KO=ʡ;] UA'|[} pݡ]ٙ=w٣ٝ}ڡ=pڵٹ}=ګ]Ϗ;߽ݯ}]]}];~! ~#)>/^ -E^IU%~A]S?ܻ Y>i^g>o灾ck>aw>敾}s~㳾W҉uǞ^Ӟ^ߞ;=-|\9o>!p]!\+_]< \ۜCO_g[k{_u\MǍ؟_m=/Й__???׿? <0… 20ĉ+h1ƍ 'y2$t>0 9|%̙4%ʬ3:{3hLDg-tѤL6}QT8XnerW , eر}j,ӴjmK-ܠrk7T~2+޼56z.3qȒ+Rν|+9˚.Ӫn}5پ;۳c ,!$H# HJr@8l@P&Qa(G`TÔ%m|dd(2R$/Aئx^K"$e/PzGOZs7i,b?c^sRH%,#uhGB>$ΙN$ <="OslH@`J Ah$DB'{ӗCOҔt^L6Zrx9R xuI=Ϗ“1LkzSt\=[;X7U1O-L*K(K'I%F#C>9666553 0 - ,+*((('(('''''%$""!!!    !"###" "$&),-./1222222"!2$%0*(0-/.00-31-44*78'8;$>@$BE#DG%FG*IJ(PJ(VM(VS&SV'SX(Nl+G-;02121212222222222221A4l:AC?~8u4o2i-_+Y~(Vy&Xs$Zo%Sk-Nd;MXLMMPNORMPSLUVTWYYYYYYZYY\YYnY`_i^majce}d\pgKhkAfn9fo0fp"en!eo!hr#o{,t5y:|;9988<@FLNKHDGDCBA@@???@BI]|¡ƬҺWSQOPg˜ H*\ȰÇ#JHŋ3jȱǏ C*!ɓ(S\ɲ˗0cʜI͛8?I2ϟ@ JѣH*]gϔ;FeJիXj5T_)>}4w I۷pʝK̰Fۢ8viji%ίÈ+^̸qKF7a^vX)ٿ ŽS^ͺgLX틐j`Oԛ!,52УKNzH䒁:{#mZq3f03o}߿۾hl'qfCk-l9`M蔄eHe'$hM!WwUԅw\`Zem M1|'6PFɑH*HaXeg\|QUfFfxh&y XڊI2ȤtiMheGbqx\;&A*j=^祘fa_hUo٢dM$c֐nbѦ䦼ukhmr:"w`bvf: :^eg v1Y;g%fZĮY_:҈m FA8~"q~'+d.Mg13uĶu ,_ -l8q<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:{>яxI=L nV.5`BDQ3b_v6A@8!/BX)x+^p{zCbczga=s Qy'cx^G>B`+B,[h©H݈bwW$vwmVʥlcԙ.H r i_њ6 *Zn[x|J ъ^@ i/;bo2F e~"$Ek`u *mW-_QlS'8A(^Z3WxSʨ#x xy->OQ*Sy%H5PR<KB"2 s]7U;1ӟuER}ZZO^zUb5VEHmWֻG>Ytb*exsw'?^,_|p&|&>^ W1ԣzWdz7za T}Q>OO>8XxG Ѐ8Xx(؁ 8$X(&*(.'28؂4x2h8*<'@XHFJ؄ȄNR؄Ph P UXTF؅^`9 Ha8pxr83Xvx.` f P X `   x q|8{x&pI 0 bg (  0  h`I88( X  P Ќ ː P ƘxhX P` ȍ dȋxX ِـ9oX p^0 Xً+هҨx($i  p=xIt^HԒ  ) x Z390 Ѐ%TiV HcY H `@DyI gɘ@ hQٙ)y) 陬陲y隫iٛ yi™ɉyiٚyIʩ֙ ٝ )W錘f \ Hdy9Yɝ鹝yƙ) iʜ gj Zp*I֙$m Xf H`vI(ـ    LEڃGIKʤ`(؀OʀY h5Zk]( xci (Y)MJ_zuJwy 8 \U1gsJ*ˈؑwZ8f lrꪯ Gx ֈ (0:|**鸀 v **Š :ɎZيۺ:Lr *JBȭJ z\z |(ȯ,Kvʐʰz>/JqKW6wG:sl#o7Un :gTv1QV-wsPI<[Wr@kr':H—qlX{l6Yh[:VK;b)K:%eP c+u}M+i+:H`Ko 4e-;[{SH۸t'[{,[k˹ۺk۷Ƹ[ H+;H[{[ʻ{缬ۼ G@O4Q[K[{۹껾۾tR)%j,R}S0H55gzS:s~lK;,;|ëLHdXzXYGeYVzVzY/j e[ßK>AlCLEGI캤^~~+~ j/   keW~aClA>5]Lij{GV zc@&d TG4^6~8=;=?5K5l{{ )]Oyp uѿ6@L> cPR^vE_`Xˀ3\PEPc^gqCi`-nAeDy^ 6%gh2Pv& 7&p b7 G~VdTz4q\_/VضxC3`&<bEV3GYYe}^Iq|8>noHq7q)lz폠 [;_7rp%FuD }Xː[  aN  Wc|\:aF>$Rnwo /W OcP,wa oC|GO^:G(?n3@ f[A/c1;ٯa~s6POه}ڇO7F\iuoc)T7peJ&7肏Cp`byIfGtf jjmBAm jvjХ?,GGf]{GW|ֻ/m&{AaT٨_/~FQG%ёZ+Gj6Pt{0gUD6V PR-`&B)BTU^Ś [m˵؇^5k6ډx u;qgχ2ſWCW` @*f$pLvHmvqx&9!dɔ-?¬/чD~dቬMXrfW+iѥO ]7oË9nI)~uk%DcO|P`,S=ءn.LDy*[ ,WfTC0B1`2j>yOCMN%<;:mN4*8DMD$DhE`IFhQG`1gH!!##AFlG*l$?.)0߄S[o,Sk83fXrdQGŊ@t>I)ԼK3=oSN?5:C%TSOE5UUWeUW_5VYgV[o5W]wW_6Xa%XcE6YeeYg6ZiZkŖɶ[o7\qE8t\ue]wu^ ^{7_}\mvNxV`U7by !u6^'25YA.y=FycemYY&e`Yi^:֋7hF:iI:j:m瀪k;l&lF;mfm߆;n离n;oub&a"O Mآ$L#?+)wu_>}o__C]?܉P;/Z3ȣ/ZX;C0/,R˲3=sL:;>Fڄ3b//w[l>+P6yz[/? GAlc0Tܯ PXA3r#3b!"TA`Bs?-BJA;6*$71,CCT;BCw3/?4d@ACkt±;*{ ;ԳBܶd8ep:4\Dkr{@{pR4dzG\HG+ŐEELFIAŘ\zȅɏƆ/uv4IH+HdH4ʡʖ@ J֫?L^(/L5\C < ?0K?xt34؛KR糯Bܯ>2L#HÿTLb̶|/KK[L/?6$K_\MP\M:+|IA ķ<0<;@@TD,042eX62;Nh>lL9M#SN眺PN:L$8Be>lCԯι+O8Ŀ/OODO=Ӷ7lCk4̽MĸLPLDF %H[r#3]ή82S> OLQ_\KL`H$JZPPNeeWN%1MV~WeQފUe`eaee \I^fOfge].XVeh6QFEf>efjo&fpfk寨eelA6qwY`:^[>Rf\ujp`= >ފY߰Lȥ]xg~fzFgv}g[n怎揶rX[[=c]in}iiii?.XP[ڄbNZ[xa3Fѝ߭jjjjj cY jQO8$2a kk !l. h8ᄾ9 $D9࿖.llDa```Ѿ`ڝު&ll nN`خnmlَ^m޾m>l^`.nlvnjȕnw1Y񇾽nxX 6aV0-bo Z>1VonjGWgw " ՛9}ީU_!Ig `ip䉝2Z 1`^*GkVi~p!Y`a:y' "Hr,!xa129 7&z*r(G`H$^^НxszNHT9,5 0b9 j JRtw"? La@HuG?Wo,t5PYt: \w hvf'}?I:y'. i0lPTS憻uaapvyv7%ph9i?7xvYҎZhANSҤn~5(hx4 Aw@zw` Rt(uQy(8ywJ gnG(Y# IyYsHwG90yo hϔ@   w/zqJt.q6Jty:!!'!% 0D!Ey{pa`'7ӧ~B,o(ח‚}ۗ+}ò,B߫X 7+q~gcPt d`0CX`afpc@)c<LG’7o „ 2l!Ĉ'Rh"ƌ7r#Ȑ")r6К5f#WLe0d&,8s)Jkd \I^)3X7 Vᱦo)˥Q,X &~8%mxqEv!)U8WӞ;Ǔ/Ur,dȈossL}|=hSuu=#WyI ~ 9ml6 CH)sΰuaAR"-"zŘt9#J!aSl&TXQe<6M\X7H?},KMFnqTrv7Ue;H%uy'ZMy:ch B= KKj8E )jfMi~舰6񣡒t*#@ܙ~{,qBT_:bp)M]YZ Hl =:Cb#l,l^1zp d]뗰,|pcZpI;m} n @8P0od 饺崰>R]Qϰt U =4EGT@Xj4}=G?wN gi( e16'_R `VX]k }TL=7uv}7}7 >8~8+8;8K>9[~9k9{9衋>#p#7:뭻|~; /$ ?<3 ;< [=^?T=?>+;髿>+>=?????(< 2 |#( R 38Aj =&O0,|! QҰ.!gxp;!Q8 0F`D &qN`(EDqN,1ƨE1-Qyee#߈8ʑtc q>\`3rd . '!M409،X /XCFБ\FL@j,JZPF%IE~; 9R7Y/}Hr},& L*s"tf  X`)eQ'>oS~5Mld M\&v Ms,)fSIzӜ ~r0} bN(AÏt%!IO@tb,F-Lb %hjSlOI:MiM,j:Ӛ" )20%%eYaҴm-'Na*: L,P|='i ®`]9u<. @ ؼRu+eHj(,PA d:jٮ-mk!ow@䷽-m+]nrGؖU.u2E.w ^Vה=/y 6w woz]{_W%0 ]vw=0~]ʖ-^jó-o!<GX 1KZH<@]ٿ6upKה )a ԁ8399AU5O\^iq/Y}G&CD?qZg9oUO璢x]Dw hs&?NIvJ0U8Zǧ.]hpb,L`y"55HQQUOt3?#U ):i"XaHQݧ>{ v. 7c,ڦŶ&Uў\6 :472]Luݕ$Jl %EUʉ|w YޜH--:3 *Ko$K8ڊ Mr܄!WvCx\fWs"3w}s$=ρt=EoǍ~t.M7ӡuO]Uw90ZzyqW=YEn;.G ]hq?'t<9 ۘ]3+?yΓճ) JRg})>Ϗܾ=/?>3 /}Mֿ>s>/??}o?/?_M & j6 VF O^M C ~`` F E}p?D Š  ҟ} 3X0 N!܂3 }"yUPBva`,$@?C !aɡ !~0B2 0B D -xMN&@x`Z"U"& *x"m"i")J)`**C t@aC bmBNl=b+Z00nb .OpO 0#1#A#JRc5Z#yO 0am#0 ""/fbzOPk4c+ʣ%R'Y﬙@>V>?@#md#BBB"OC CBB"DRCdEF#LdE#FJFf$G>dHdBdGdKFGLZ$N$LG^dLIdNdF$Q$J$ROdQ6ReK&% S$TN%ST XeT^KR6-z#8bPpc d:F'4XrXdXb%;J#%$AAZ^e^~P6fW$cAD$aaR&b2c*V>&UdCebhzeffAZfVbkD$O??|3b#f&c* )z."7[i]#0#puavDpn2Şvrqy"cyz^1 ba-8"sF_|Bl]/c{^_J_2@fށhfRhN_zb"%4a|2[F'\%fh=^(^(E挲(N cv) i ƨ( "*)( (>uF)R)*f!'n)%v)~fߘi~jiFũ)֩ *|@ݩ#?괂 *6jx~N**U*v*(*ڍa6B*MN*֪*檮**+>N+*Ͳ"PłDa@$D4CH @!<B(@0"f(lBb+& 7"v@B/D/<@#+" # !Ђ0l D"D!Bh-z'D]R0..Z-Nv@tBj@4B,'*&. D/fF|6֍ @H0(*>.:b@B@" .#m..yEf/B(n"*Z*Dӆ,B(yѾn: 1boф/%!+"&/抒*,#܁]AbLoBT */R<&lBʖB  S,0@@ď@度!+{\ʥ#;Po 2 00!B  oo2 -4pYx30 p|Jb\e'yeFqP< #_q}&0&'?n/B@&p(,.1C%..?-s,2/#jmynE@|Be؝d*>ϖnҧ}O~""#$aȊo00+1#r>b4WsB!$2l34qb#02g3?n5'Ny@؁6>4r;/0hxD8Җ81t8oy<`8GxyC_)Gii$Bfijfy՝K&Ĝa0"0:ya9}39_998|;9wx`c 7CӉ𼺖_gyM~:W:xW39a>'Kx#wK{@*^x7ot=yS8'8Hst{|y {3̿<<'/<7|@9<珬N,jo|\`;LJ|@|9` ȱ˳;{"ļ KGA7=O#=ģN6O=W_=go= |=؇=?ٟؗ=ڧگ=۷ٓǽ===޻=>}>⧽'>7>G>D!!T}w~؇׏>>>뗾~{>>EB}=A.A 4!ԁC(}B/?W7.XB((p?@! TaC>xhWŊ9G`\rulA( JT v,x0!D7Jq!9* R$C&Qd!LB2a9N>'PHGOLKPiRUkSZoƭ ghϟES%pÓ3ޑbǏ!;l[EA| `$7,TS>Ruk  -;6|0s&C0 $hPXG‚Ш{޿HPva (cyӚdN1}WN#PxS</;c[!@0)C,;8+o!%$ !j Q0C Th<\047SiNQm""guS[]8uA_LCr#UUYt{S-^v!v:0<2H˭"gkPNvYf ^sAC]%&3l%mWw x F_uj!]&D9f:@VHZ t< %eW>R8 Z+c._]#z!k 1H5Z63Ky;.4Z5oVr6VZ9;RaoVR|Go/M)y_5YAr3rGg|kj/[~ؽ)pw|{U^n)g\!{tѝGwr u5!o^|'' |=}qs^?xo쫍5cW@ Ѐ %@%H<ҧ\BakQ]&8:EmZ8=5IHWkH^%rkbTfVpCR!ej KCj!!0 9b(&vLU@좛d4E1VDCle_txQ^FU%9dĸ!'W3f"5nx$H-qR)8"US;$xS;t@PHԥF0|ԡ<5EUR/V<@,u) +jխV*>mjGȕeK/ԫ"4hВvVH.<*VkEC {6K dnvQ1]jY+q|h[[v"խ\x[ݭq!m6S.SJwinur]E͓w^2@LwK.E{ 7}-D޹V xVs# ƒ!a;X1a o0=b!qQbw+vWbϘ,qa5q,f.\d%{8KveKnr|e,?Y[^r\d0  |=&sfY8s}*Y0s (Y&>;.tGZS4)}i$k:ș4?=PϸD0C)h"xzfu#jX7zy5o\XֳKmS:ֻ浱lC OKt E; )owmnr,{f}uw}n{򦷺 ~pyp{Ox'o#%q?8p}|)8q{\gq\yGns <"9 t1ǹЍtJҡ~uVzѷN{e8ӇtOf`Z۪^Kz{~me/{)K]D׽؅IK~򔷴mkʜ0s S5|>^Ϭdz {9Oc}´' w% |/+pM#?ק ~1GП>g???? Ps,vO,.p9$p/HJ NRpZUL@ lP2_V~ǂP6eL"Lo#= Q L = Բ „ OwP0|" w cP 0„^.nXm.MҮn+W'1 Q/P?Q5(-+QO7WCw^ci1XqeQ#$GK0 S1<QQp͇{qqrQ1qq5  @ N͘0 a 'R"} AP̀J 1ΐFR,2 )"!+#% $0$-$QZR a w"{R%eRpR% il؊R)u(R2٢R$R( l+++2,r,ɲ,,2-r-ٲ--2.r...2/r///30s0 0 031 N132%s2)2-21335s39.w:3Es4I4M4Q35Us545a36es6i6m6q,fr738s88.o!s9993:/M::3;s;5l!O;s<ɳ<s>>>3?s???4@t@ @ @4AtAAA!4B%tB)> 8/4C+>G0<;4DE5*HTH4L6`DVóBAF R`5EU5JEEc5F "[> 5t5Tt:rWmDTD X K !85F@T@5.I  A TCMٔ5M5T5QNLUC x[T~TQUO-@5,O/SN-LT'M`SOK, T >51t5@E["^5Vg5jVsXWXRSV5WY#T5sTQu[  JWURU2YUZ ZY^͵ \@M-]o]AY]m4W"u5_ Z1sH5b^bU^‚"@[Vl]%`_ ^A^[^cY#dYcdd#HL0HsU+D5H "L fWfqV².@[-:}bA?v4k_uPN!]U vW_Wk[]W6WYL]#n53'ToG![~tmvY7T!UnWA p7qtqq5l9AT5ĠRd@V?R#( 3EO5(pq?q$ʀhsM!Kdt%Desw+BGW#x5uSyF%t>ti+W56zUiW7ww}rC5C7~w~~~7w8x K:r4!8%X4OM5Tv1850+6A8rsEQ82 U]2 @ii8 mxyt{8x8x8x}' 87MJ'vtR}KTkJY# Davxf؂/؎U N5XE6h{DStEWw5ܸa-|ӏ])3{h]Crp M SZS66b8s/eك֕]yxu0˔\PNdQQ8Wykĸ'9L1 %pU]5oWlW-8g9ك)V9 SUYUX`stq5HLYrՄvY]>U۹Z3:ͧu93\u5W|#`"}9ߙ)5ь1e]zhVdwdF{g0="XCWC586f,kւ:gy6e`ngh1̀5mvmVW߶]UY/9i#T!zm0뺮Wmr՘ 3&Y1s7 Sr-w5P TWo[Wڢy-;/Wֵo 0wl!G[qdsWOa79iL㙐+ \4G8mJסۘ|ۖWA-g5Z`0ww}x[z5{֨Cag۹kڹ[aۆ6 |6||!<%|)-1=9 a)}UYaљ@@bOge ׁ]Ѕ}؍ #  ̕  ! $*@%਻=E=)O}̯ܿ|E!ܳ=ّ]< X @ ޵< F%د<~!>%~)-1>5~9=A>E~IMQ>U~Y]a>e~imq>zu~y}>~艾>~W~0A>>4BA@^:~!~ >+2!,&%$#"!!!!"$'''''''(()*++,+    # ' + . /24555665531-,*)(&$!" "#########$&,/!4"8$>%B&A(?)?*@*A)C)D(E(F'F'F'F(F(H'G'E(E(F+G*F(F"2Q.Vw4g9t<}=;}7v4p4m0g,`)Y(W{'Sv'Ro&Ki%Ge!Ac"A_#A[%AW'=S';P,7N-6N-6O-5L,1G+-@$%9 332222111121 +$%(*+)..-01-78(=A$JL"SQ&OL,MI5QF@TKJRNOQPSRRTTRVYYYZZZ\Z[\k^o\l`bxcRmeGei@ck:dn1fp)hr!hr!is#oz-u5y9}<<;:<=>>>;j893222222222222222222=3p8>??@???ABNӂضŽz[SRRR H*\ȰÇ#JHŋ3jȱcGCIɓ(S\ɲ˗0cʜI͇ AdOŞ; JѣH*]ʴiœsiE?Dׯ`ÊKٳ Z]PLn vmKg\ur8߿ LpJR %Te߃ޝ1ջT+G89^MӨS`ry++^CnrA̽A <УK/|rTp}|߶Y=n˻OOIVVq]O)Rk=6rq^nrUyik!|u \_=v!{D m馞% :ՂH!U!nrȢDiH"uV=J {OG%@`SwrMl'-cQgQf@Jcd⹥䡗[fX*ehpF*餔Vjb%Zd'MUhgZ&d2 1gVꬕj뭸ҧch\sVw|[p (2"0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6]H9Rx̣>IB$"FR$# IZĤ&7Jr$(G)IQ|MVea)Z⑖%pZr8F/[aD%2ʇsD-Zؑ\&6?mn$8iAF3 9Bè#$ =a0蓜-)PGPg1tH D9a Gcr4P $(Jҕ.MdL)c0PF;Њc r2r!Lb4pwJֺr.sEKZ!++2cw >3f l>Lf$INYG./WwCxָt"uqm'kVӦvK)Ger0tZw)mJ۠5*GzβRu2ۙ 4yw6H:VƷsOʒzUv9 ox_:`@t\Ua9nܟG??K^ut8qJT#0;rDzUysO,>uQ̶յWۼ\lo9vgz[ώ}L{.mݘwfkNwp +'k0G-p OG#/y_|R6Oy;19S7`MH@|)Ã>3??;_~쥏8o<{#A>t?T>Ϋ?a>_~'8P W||~}}'~ȇ} y"@ @WjV>*xf,+)824Ȁqf/؃-C3hF6(D8K8QXN(@$'~y8:~]b| 7ၛG~wfWl"@cHh'jh (8 $x e{ȉz8fц Q6A}{xy`(SC؋85Ǡ ŀ5>8~6p t @ Qǐ PE ǀO0==#PM!IQ G H P a hpp 0<}@ 8QY P5y}H @:6i$؈IވX H  Y+ْz ?}< z~;)"V؄IWF u Wx$PЅ+Yx_ty7_+W]I$=ȇGaUY|@.)Mِ43xx9| @ pI } 3𗑐{՘P` `Ah4|iBٝxzYy虞깞Ȟ(Y9x왟::ڞz ZImzڡ j":Z&ʠ(,ʟa)搉:<ڣ ;*DzFzJ*Lڤ PxWx*ʥ^`QJ-j:hkJjڦ|zW:U*poǧAz *HJjKi$x*Ueg JZJ{ ꧧʧJq7Z8ჹZAH{^j*xJ j ŊuԚ*ZҺڭ亮ꭞ jzz㪬:Ǯjʫz `[ {Gj۱Z!{˱ +& $( -;6;:@(ʲ*˰?iuJ[*﹪pmʴkh f* jQFUJa ^*\JPjMJGʶD)P5j3 @=:ʷ/, qIKjbʸ)& #* JCʹ )Kj䉺ʺG kE:k ;K['z̋/Aaρ&[ih Q@_ s?pP0؍苖܀[&ɍC鴿hѾ=Fޠ KG1ULhOSqcŐ`aQ'2V)7\Y)@ q ɐْ/` E yQp\\ )OL pJi\2,N,Q!_LaܐrgjaIbL|39P oL ! l N!MhZ IL,I/YRl©!l<YƮg&<P195lΏ$搽e^;}.^~舞芾腃OqS3G>3w5y؈8J 47.;4W13~G ~KGHbG8nNKÆ(+C4Ygh3h8:~38|y~>^~34P쑎.4 \%:L~3^4Y,Æ\}<9=C \h2,/ 0)3ZzK-/?: ]L Q9UaL8hAXGz~Ȏ:tmjmn =w{Y;"~;`3b/^ 3.<Nk^bN8o??_ h턡0ak/a0a魟G^/*!ɧoc?mOHuHp(K!{ׇ֯4X.WAM@ Q" U!Í-NRdDK&QaJ#]SL5m)ȝW~LSȂ̙5Tґ޼ tUԩ*<&U]Z2֭NzE cV4ѾУ؞me͊\@Xu!mV,adiz]裞;ٳ!ͼЫIxmjѬUs ;l[}{nG]@]ͭ s]:^]zJÉ5$M@PCE4QEeQG4RI'RK/4SM7SO?5TQG%T@5UUSfUW_5VYgV[amV]w]s5XaXbEXceb6ZYZh6[dնbE[UoύUT5]wUUUvߥx^Z{ { F` Z"I|fHfiUH&UH2&X؇?Vc=cb]<YdM~eS5٧]7u ONyeTWUudoJ竅&+eV}:jjzmTXcqml:zhnU;HFc]yq:2rU>H ߤgX'Wj?訥TgYfcnvZ[9fPfHw#:#ԋ㘦GySϞC~OUGᏟu::0/V{^GV+!>Uӟ|PeAUEBh؇*=;Z'ش/U(!7>T+BGVUB1fg-y0Y`*ꐇޡ@+* R!η<ʃ:=2PUgJo8E:t"*jc&I=Zc+]UnHD.&:QYvx?bq"HWxtԢXAe,U|QU\6i* z 'ֈ:RLoL IjlٲX!ˈCgU4 u?yTQ0*zp͕[D%QӄH71 ]  LbuKcNj#ȻiV!k<:BepdaGc!xDWUu `5@ճj%jϖVʙVjue}k_:VO!MZEի+ ;æj a >:<2R6*AVkf)Y˪U-6?2iMVU+pبʣd^Sms*\ʶ] 4n]ƌeu*ҁdw^Fwuop[W=$B4DI:Mo`CBMkx#2UEAZ*p' Wr0.m۫Hױaİ҃] zbL tqs3)#QYyۃeCdnY%q0x_S)?1\ F^|pמCe(y;|YqDHbz!ƒc ҹM;4EǑC9@ZetA-r%-gMϟL97Mo[R:3 wZrzfV1D\lP"Emtղ7*h{ f'h bNƜs˵rQ*mu.6M)þQpmpo \t`?^c{~oq[+ {R2nrs'S /'Wr{ Uyt#9Lm3ϷȎa/ W3h/UWd>Ynw[}tOWc{va%z#+O.3C_c~*SoOkVӽpsݳ>2U?|G~|7:ȓ\$-}w瑔t9)I_j!BG?3Di*dT?!$Ԑ s@  9;@Є|`? $4hp ,-9a3A@,1$24۾3T5d6t789:;<=>?@cPbDAHCH89c (cH^`"؆cxAH@ 胡3T?YMXCI[bH8eDj b]X^0Ć8SC8Ag,CXlP!epq Q] F//XEjj fCTO {?%}|DG~,G9N|N,_dddsLG= (A l(<LÂBD p,$Gos,/Ѓ</EHZmʩԔrǁJjTGfG. ! BZFt˷|e ƬE^e`Hȅi>pGCF$ès@DshKd,J<^P`ĆG Utќ BKDTdtׄؔĔM4N$TdN.ΒNM M$dTO\ tOOOD,>1NEPUu= P$+,O=QO]QNuQیQ4l!QQ R!R"-Q$P%EN&]R'NNJx@R.RR0 S2-S=@l 8zX۸S< >ӁS@S S܎=>TE@}<}ӊԂ`HTj GTI]?TR JETuCIUH=UL5UXUQTTm\SYU]YXIUbUBduU^x`TY5PZՓTUeVl]_UgbRTdVk WrmtV`}jx֕hfW Wx}W`WS 4#-RR RQQQQБ8ˢŌMR=XOX}O-YRN]YO),< XXYYYYًeٖѡ-X Z m }Z ZZ4ڑEګUP=POOOZYړZ5|[Uں}P][s۾[mۿuY4EU\G1MMiM}*m R0mD\PJ E~̍ \NL Q,Ņ]5B(pl9 ē*Ju^Hq$e(a\ctHedFg>h]^^Fel_jtfF$,REe]aax݁_eG@@j`e`(_ˬ$GKtEnHOʫL\`^#$a߆@0`<n ~`\a`d addH` $ܐI^bjI7ɇ8 /1IIJ h~2>12634&5nTJ6.J$-8J>.J> Jv ^L,L fN ^Tb#Fxe,n<\eˆUe_KubKKBaP_KjMiF=LszJ)LDׅݱz&^އrVm(ΥgЕ]=~FEdC M ]_޹#fA6ce=F NwN_^^w|^äXgnNale~"w6q)Lpj`si_6|A^'f>exh@X p4DNV noI0GbeȊ>վǚ,:6cC^?LHqK3G/F7d8rH2㜼I=>F6JT5x\d `eZ2A^pxR}}Zpt<4|ZxEUqbH#UniU794 _d!z D&hFV5TG)NR~y3B8.Tc)# VtYyB<#iTo'Av2U{'5|i gmʉ(w.zgY)V飌)Nz~:ѦJ *ji:鮸:ls (v6yw7Zlӎ+m*imhbwꯢ.dt\{Q ALʛoۗPE& blq1Z\#ohh%UcH8n~:BƧ˳L}2[RB1HLv8SxYUV\*c}'v*-zԳ!Aڹ=}@L!(!F<"%2xb()RV"Ȕ'B^b109Ŧp[7:Ee#=Q{m#Ek#"ihC2)d#)IT2H@v|#(xI<%*a562\e(S)KA:+YYRrSj:R$1c^2|!)brԄ&"?󚰴.LK %!qpnS|'<9[jL&85yM,'@OYS':WLygL=LtPREkQJtG*ґtI1 R:%(::v i;hvҥiL*S 09mJDQ*թg:c }RՊ*>ϱ"y$&TtZfX*׹Չ<7:ֺ^QmkBUf1hd1U]rӚV%a#+nl7J&.2,b9=-jSkCӪVm-lc+Ҷ-ns򶷾-p+=.r2M1TQRw>xS! T] )L)1Ra zw F Dr/b)GPyAbM2x1]8Si X >1Fe&y/S` LB8~z`jHX~JojBS`R CEn-,)Zfz%^m7ɤخ\l^v8o7.(#L |ZâT4;(rS&>`Un vQ@}@U7wtye,)v>t9f5FGhDMZ>/axPDJ<^+vudp>7ӭu~7-1|}7?83W#.qC|18=G\$?ogcq<5qs}|2]|D isKTƯoZĿe\b x.w.t\t?^}GA{sګݶK@cQ?){? {PX;%g=|`|+L>K)ЇsBzLScݏ:{׾._? `џŝ-`% ʟ`=F`e^h`b ڟڟ v` V ` ^ n` u -5}:_?`~ ֠   :a . `!J!`f!1Ba١!!!biϏH;]##]$$v]%n%b]&V&-]9uё"љbС "ޱ"ϩ\b.bb/⾩ 'c0#/"c#91AcQ#YI#!cݢ67bc̉}#8j#9֝9#/v2:!@ATBې1E>SxCyWzY57`S?h5>AA7({]${|}HfJSב1 Nô]L6G$S1C.<*%1dI$y%NrdF$o %AJ@180TdO:S`%S`@ T`$Ղ#p&CQX-X5QSTS\ pI&X%5YXZc֚^/SUR`f&a*eRR*BSB.!SlfDFظ5ivZ 5C)̦X>Eg~fh&SQW2&AťS%٘q%}b5XNZ|`q'x6ybuBhBEuzT(0(X]ZSo>!vS8$42Ey>AŊe~zڔfx( hSt(S4RhS(j՚ Tz({gR(*SvmS(MWiH&[NLzÏR)5`@}!fah3ꨚהَۊtImd֐B1l٥љSٞiJ@@[!nלvvRJ Q1PmWfj. Bj)JZ6)]6A ٭U|h1jun M:&op{hUS+~Rh. 6.I+ah~B%-ؙ%S&%XxYvo)vOl2zڮqҤO1_Xxagf,,T*0.ȫ CvV$l!po2Se"lӆIJۡ]>y5I[mEX-5`}mZy,)7FWڂh]:ݚގWZZ9ZDn eB*R:E',)2ERۜAVOz6mS4Z+zXneB֭FQSޮ.//&./6$=w S<^/ Sۡ:Ngbl!r>$crFStί֯>+ovBfv/+/>j%p=w=ZaB'S|ps0 W!gp0 co#/~ o=pzpdE0'.O1KW1go1w11105qJ:i{Yej?pl寳1 Wf:m>nronтIq G2n'kz{^$(_,hhcFa1X)#-(r*A*)ǰnj- C-7f-m,XJ@>3w3jlJ֦,="S;p7dn⪭SM.1+ɓ)ty|ԁ#;?{&p>~ǺyꓼїG|9#~&>~B7}Ó~&>??|+?*p?~>W>:?@dS:%Pu SGbL 0ӨQSdI'RCeK/aT )Pq,0O%R|hF1dLԠF:UUWMhUR.D,0}TDF Eju:nJ_,kAk۾$RuϤ9زgx`, 3E\WR :& 9Nl+vyTN;xҸ> ڡnIB]I ǾI}I3)ݽ+UP3MbUkBƛsM" xcon;?2*hP#ɰJ$TP9OAs9N/BT&p Ȑ O)E+R! Fi4pDJGxdǻɌl IrQ,LLӮLzE K2#=\7SϹT%夐JӵT@޳c.n]ЕP`˦[5yKessׁl,w}Zz*-^26A,"==.zUp%{ YDEN_6_Ge}>o~M9TrB 0t@)p; \G0`XA%`=ql(GyLa^[D4]h"!Gr9O*VpaEhd酓WLd4cDP $"0H#5*yRBC && R;Qq{c#A>* %,#i?6N$). L6BP~ XFQ㔩\o1:d&P,T)֋#%)x@Ud̖ #Ғ\f?|JrR6sg):ytw('=?&'p ̄ii hC QJo=El@?t,IOєnt,mip8*ΗgtyhM]S@631vs0{G^-s&* mJ.pV`/cmMpvܣ-7h]n{{݉`oU܂޷q5oݶ) \*Cz_mֈ#w8U،SU81Ɗ|$ǪOȪ|,ˍrk07n];9sY\5σ]/}I-MN冼P?zձ_=G]tZ=dgEj Zq \Z;^"imV]жek+^挏#w®ש8<#^/yz=KEp x⢿o+='13jOsZ5,kVw=Q#g]'گu|]Zʗǟ}JCܧ~Z}O|7d)&N oʏO[-ү\jVNoVO360*: V:R08=Wp 0ސ pnZv\z0 >0}dP # ׭] P y P* p~p0ʐưְp K 0 [ p aP1 Cn[*0q Q# p HV! p K O J!ac'+1VQFk1 G1AldÏL*LĨbŨOBˑo111!cM/, ,R!5!q#3!!1$I"'R Q!Q%%%M%/ "2&-M~BP;`Fp-)-*ŐM*j,-0߬߰겿2厲**  R.3Q-g) 3A34Es4I4M4Q35Us5Y5]5a36es6i6m6q37us7y7}738s88s7O!L8u3993:s::3;s;;3"NAJa<}#  *T3:P <===?; 4SL! %JhN!=4$@"o4CA4DEtD?t t b L4*RH9b s *Ab :C?b*TEEc=h*lIkFI4J@*bZ=J9W*xtG{@=t4MtMٴMQEC<?aAU=@ B *rt:4*d,OeT*OMG4OAK"R'u*TC;(5TEuTI5D_4N5*P*,5STRa*6U:*ruU[^5VuVV!UXT3N!0bQBS5Q5t5[u[U7;@9jAWPɢ*+*:`OjXj འ[Oa\[t U*U U];]5;ԩh|/]_vfƠ_ Tvc9c=3dHGV!UPPIv*5*!efcd9DfbXCTcuhhK<3EW?Wbf4 4g3WV6NjM=j!5ej81 Rse"hJ VCCy)moWCS`a}Ea24*UN" 2L:w4sN bq\3rsW8m!<47* @b3R7tewvivC97wmwwyw}w7xwxxx7ywyyy7zwzzz7{w{{kA{ɷ||M }ٷ}}M ~~~.HT4ko 88[;2Ţa`CaX-18k)*5A8E3**pQ8UxY]wdxiaq8yu8Xiuy5uA3Xm8bYaq+,W?A!4vA@B !o8X5,Ƴ<Js=)Eu˗}8bGd@^UHdU4I6 u19CDY5EW4K6IIwl'{yB~YW_7qtu;uitxpp7sq׺BH7OS:=z;%{o8)1{-;9[6׷[?aӲyZEI=^_U{^# Y;W۟m}ӵcյs:CiaS^W=;]}۹ع[ov|}{;};;[Cۼ{[۽[cκ{\|9ѹۿ!X*\-:3<*6};*>\CI/g q3ҫ; |Rٻ@){*"o|8|{ie|CQ}밠;[:wܾ#\|Ts{<|<X %} =}}!=%-1}95A=3]I M=EU7]=3eq}O}}=]ׅ׉FOoؕ}יٗڃqA|h]ڡٹսS=ŽɽWϕ 49 Lϕ = =FA<>eѝO6D= }HЕDV >C^ ړ!P <%ڥ@Fa~/]a>#}i KG%ϕǀyA \0^ ~ЃϋϏ^ @mss'M!ޥ ^^ CSM>ž^> } ѥdF^ AГ<'1I]RI\^`X*@LP_ ]A=_Rcca16}_K_ у>!_i^@{L^9"A) $X` >T(3bdHĈIȰƓG豣K1M¤ %F8-'˙Bo(%$ϣ2}I)ҠR;.TU+Vbn))J|vSzvK)0h1{ڜjxeMC2<Ĕ޶xR".^. ׯ{"6}l׬&)l(TbZ )R) =YKֵ߮ 1zt)V1\(I9 {>ˏwl>ӣo?ۿ?>K ]uiV "ƈKY(\Yz_ J!߇"~R(g" rd0ňIG#AR2 C($e%c(d3d~I.L>c" CR^-!مy[ybo:6C&R*u ٦9{X#.?J*b]wN*)iifj䦜 pocR]yyG!!vʨYq K:kC+"z ߉ W ,N+Hްv "K~+}BG[df{to/p tBMtFtJ/tN? uROMuVWAWou^ vbMvfwugvѫr*)=Pvtwv]xkvNxx=wڍ8ߌ+NҢ"CM(Ѹt 8.㕟zꪯzM wCz"@1y{~t-/|?:ߞ<__=c1^nk~>ÿ|O)pŠ e T`@,d` jpa)hB >'d!)ߍ~ӳlgp<FU~+"(̭{G;wC,t\*j1l-XфNOz_8qfF6#@ Ry^!D*rl# HJr%/Ljr'? Pr,@b SV-+XrK)ҧ%(EtbS( :dXU0xͬ55S1@.S"ZhcRc [vbS:)9|sD[0C 'ꐔ0cԹ@EWjtoa](|I(E* *CA" :^V͋tO7RtʚH utA 7juE@FJ ^#)tUtk5BS|Ыtj#xR;O&L`-kJUNхBD3 N\0b-iOITvmk_ vmo[qo q-q\&wmݜ Wҭu{jns-s[wU/{K?ʷ/|k_7/z7npqhEX8֝0k C7nr81x,n_ x4ox< yD.$+yLn (KyT,ky\ 0yd.ό4yln 8yt!,+6O,8M);Q(@V'F\&I^%La#I^ D\>Z<^9\7T5L0J+I(J'K&K&K'H'F'F(F(F(F!+E%-E#,C+@*?*<*9#(5-+1-.--0+,/)+.#(*"%'"!!-/111110-,/46541 - ( % $####""""""       '(((((''''''''&%$$##"!"%( * + ,,,, ++**$/-111111111112222222222K4:;:::9~8x9ny4jt0hs*hr!hr hq ci"^b$\X$TX%PV#LS"JP"FM%CJ!=B"9>"=?$I?-M>2QD;PICRJHNNMPQQRQSSQSRPSURTXUW^W\^W\e\[g`W]^VX[YJ[c-[y2c5l5q8v<~@EJOSUayްºȹ}maSJD?????@?? H*\ȰÇ#JHŋ3jȱcGDCmɓ(S\ɲ˗0cʜI͛6AN>r+ɳѣH*]ʴӧP)PϟK^Ԫp RÊKٳhӪT[XsV5nAD r]˷߿ ,U{ʝ .Aş;udƄ3k̹ϚAȈFTMTnbu^X}v( N<ޤ]G+kp! 9Vٗik;_ӫ?*ZxvBワ;oc.Y~{7{CgjF(oV~AHz}8"w("@ j8ۊh8(~58`>i_x\By (dJId 8]壎\v`b4|=Ӊ+?XdY$t&)rJ'0^9qaj衈smI%٨t^Zc&aW2B|tYzs3&ƪzvG揂na:6|(=Y~e{F m}:FDxdu5%)D6+覫.R}jm#:ٷJ[bH0BnU G+,gpn f"l(p,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H:h<'6pHG7x̣; d(B<"9HF:pL$$'HIR򒆴$&79ȇQ %A)RƑL%QVցL%,cYY24.TqMڲ|9aF@/Ie2|f%hRSּf&;6)NA3hEeS1F4X j |e>1 g@<5e6Dž2eD9KQ0=Y@c#J1 ^Q`h4,զC+JO6f)2aPBӧ?4șuG]QjPTyFU£mF|-M5 Tf|<Vꓩۄ+":WLkRz5Bm*8ZVl*Wa\#נF%׉B$48#2ayt6hG:Fv -iEKZӢ11-l3V mGlmZv'r[3ַo[[32Wmp;\ږ].v[]ݮt[Nnx[ﮗ5/|+>wU~{\o|ַo|6xQZx7`30x.}NvJ՝u,a:UQucshl!p1 8fh\a WK9|bSqqcûvde:沆Ygc}fflh4ՈiW͈cR6tHnZ5l+hc#:Vw,EHHN3 |=F@XH @7`cn AuO^׬nu:|S(gF6Fc4"JT㞿&=)G`T۟w}JrGoXKZݷFxK73 L~w.r <e«yp7\iJx[kGnn2[D1܉2(*c5gb3lg@ЅޗE?ҁ/=!t8[No:֍/^G:rS cGb٧Q1xϻN3ij\Zssȃh?O.(G+Oy$J6jc9өz|C@HscMGO!oI{^_qAf_{~GU{^ 7z 72;~4B/fE.҈V>RhpfyRQ ͷ(%+e|{@ Q (P f  ]% Q"H3H'+N5X7#X @,(#URaQ R c\~~X~]`؅K {p }! @~c~UH8 t&Ni!8Pт(ф;PRh@舟OAwaUKhhT1[H5u~^~xGц8uh;p S$8g g{ Mq2fNԨֈn؍Ҩx|pM?X 8xhX(!s~`vaȅy{c~X؋QX|R2pFuc:F{1c9iF2fgbF4chtJaFF=FN86O8 &eU0i>1?VidI2iJt]I-SeLe!VaKNSh$[ hT0Oe!Pʸ7g((%ՏyX-X Ky I陪hmW~Aqz苻HN5Y97dQ@w@uh`W|&0 `?v$}I).鄧hjHiLqNH.iƟizjNqؑ1)axi_8MQy !D7`m9g Y1yh؀1m Xz'Ugt gmCQL{wJlxR&SJѤbΨKN:}_*RJbVzџZR?WDž)pzGc{ڦ9: J4w:Z $kC qk41f .Ū'yW|!H}᫽ʫ'kDaȪdɚdƬ:q6d j,d-,rz蚮꺮ڮ3 *4J4;wrLa~Q~~JGa~ 밺(ILk[ٰ;+1V:U|N,: y˰ ڪq "jNʋz> YȰIMJ[$JPa29V s*ŗV~K;Zljh³ Y[*rR[r N[vkM+4+!T1XWS{jHq=SfJ[z;!;[鴙{~˹M{ R+"J{Q{" X58sO˭p:KJӚὦ{j䛾 P{Q۾x1bIW)ggYcIc[[{a1hݪ۹wKZk +  K !\CH'AaQuh!l6x8 FJ;HSKE̹8̴ki?(qq{?KѯqwL3۷΋#wnL {1\6ţ{yǰ}ȋWiȦAꦒuܺ\9<,3ʮʰ˲<%S4'^FïZx˴|@@A><\@ =]}  =]}m"==&%}*).ma-ϥ 8-35}9@] FG]D8M. 6-/VX# @P NP ÐP O Y\gm RU n ru}ڰL hl}q=׋zϒ-zmAϮt tzώ 0ڷWڦ֝خ ڏ-ړm۷ikۉ٫ء ~}͗έ W  íϱ` , pշԘ-]=ް=ݳ= pY @ep Ϧ n= p.p ` m` N!>&~⧝.157N9 >.C>|ILp@^BT  : >]eP \P ϩF^H,  ]mO[+EςNn㊞Mp | [Хl pl ѠY.`븎SLLѥ~ >>l hw\n\蒎LS}Z .X~խꪎ  .-][.א`> -~`)?ۓ.02O58;-U%O <`_:/zS_>c?~O-OVgzs~^9?鑮O= txO0K rOnkL!/??d7 _/ו`帟ωhnďl//M~ݗޟſM^)oNOy/IuH &,' VaϟR-&C/hDPb% B@ y޴SDMI4Lǐ/:R%Kbh90u'RXXB{4 'QҽMn܅s#u Wޜx7(ೃq83cI|Uլ"Ma?~Ph ILyl r$Yڐ5X$[pō8^f%kc;3oQ2z\a# |躩z||go}Q%'7N$C; ^CI6_bpɁkH0!\ Q+0 ,iF ]1F q=$$dtq4`JS kjUijRXh%Q"@zBc,ʔgI'HJ*1r,j$/3,JIJi(.7985uPk :R23\M$QL5J>sGO])NKUi)47 I1JEih0,s8^}-XHhGxfr#}z|FInVkDb|T^{KW"}_nq@m={3{_ bݦtaMc}Y8^p 'f0vޝ}9f5yJ%zj#gj3k{luj{ƀk~)g +řY G\`g*۞|ǛfrZ/G)r7/1|tM8G[v_]Qkve}wi}wOV=!և'ؕ䝿SxG?7nQ?~}/{秿~?`8@ЀD`@6Ё`%8A VЂ`5AvЃaEAs@3ь:p/a e8CІ7auh:У2~шGDbD&6щ!:<ъWbE.vы4<( kaոF6mcG:uϸG>яi<~H)A;$rT$\DGJ[&yLd$5iJғ$(IyHVҕI!AҖe.uK^r-{9LbӘD&1qO0<9MjVӚf6Mnvӛg89NrӜDg:չNvӝg<9摊yg>O~ʱh@zOԠM#APS hDPVhFQvt)?gϐ5iJR4.Jc:S7Ԧ,iHqZSB9Nz-<ɌեBujEՈN UUn%g#NS-)jъ5 c4h5կs>la2e*;f{ (Hg5o]1Ì(VsG񔓴buWg> |4bF/Vy%y8R Fm:*LO:IǞvN)ћ.ur}^:؛-dPSGgs*`;Mu}f{LvG Xz6|%?yWҟ >RK(>ÇZ+pS +@j|hH@:>"ZjU5VȅpiVShhnJȡxe3B"8j-$<$&T'@)(,C1¿T7$ U{BiVV@0C2ii@d9 F,p"-.ѠB,DIDK0MNTT8|E {4SN0j1rc HH$P"ń`EkVt!L#lRK#YFGEZA{"RFƁO[!d F Fc EUc5/F̠$QSRK"l5tH 3UJ9kǜC+(|5SH2H5j%bǚ| eLD4L|BPt ŝ)IU,7_ W76E7ũ, Jhʟ VáZD$K($kj@KklG"DKdKpˤLŸ\JLCZE9uʫܠp)Zā8qIk@{jrZ*$  SLA@|@LM`9n8OtEvlCsdD{<UPḾxɼAѴNc=3¿$4DTdt$3gd J 'J&xUdag ?* "fj&%5EUeuCẖDC чYL˫{Q MR"M0O ? A$X N7!fhCβ%S@J`(^JˤKD `a~`&`v^N> ꡟqy@!F"v#Ό%&/mQ|b+RДVxbp~aST_Y{)c `lm ʡ u""qb)d9dF^idGx؀{ -Mn4ddoi dXTTxMVeC?Af@N9fIfYfifyfpHI@f/1iHfTWQ1g噠4ve˙q) yy ؏]G^F9hy|\~U8 lNn HYh@  v̰h`llp؀1ƒyc(f8f gЉx꾐& P pjXތ.  ðj0kj F^kN빎fj ^jj벖k X^l拿>l.l!pΈpH\i꼶llp mfސm.lmF\^lȐg l8 #is1flƝiލnt~Hnqs.seء9i5 b~dpampW1g VN=Ni  7_8Ve~ gndfn1yqoٓk417)Ah.fq+w,o-׍a$*O+s-r./s470/o5s2_d7Wp9s#fs7sB7CGtlbDwCxhO xK7? NHoGs(_O7uSR?V>PWu;G\w_`a'b7cGdCC? 8 x *`}4RJ J8!Zx!j!<|⠇%x")"-H<8#5x#9|Ĕ< 9$Ey$I"=QJ9%UZye DXz%a9&i9&m&:hJ'y'}' :(z(*(:(J:)Zz)j)J:* _̃RȔT>r #`J-$>NV4hI5dSͩ)CS6uOv^Ԣ%qK[c0&2=ڊ%1{ ‚I%pE )Y#`{'}KR%hM5p8$R 2Hp,C)$ (5,qAB)֎8뜝#- #|/~/$ m!j)zRH `- /|R+M4Ia/I4Ҍ$ͽn0OA&UHD0R3Bx ;N^9I(4I#>8nR3"$B3'.ƹV裛Mw&ծ8IxoL3D[w) T5#E[/f҈AR#RVGI^Hn !>VjwCjpl\Kӈ{k|î axHI(Wij 4d3n H5\^C.֧\|{9 k%a$s( Nԡuxa#Bk!1q#B„@^DDq. K!Ef $lJ>TH$.N"AjP fh0H ^#JRUs6*⨑#BvI3%M}u#IGE2)+"IJс}aC)̑첒P:`$)#؊R|l) D.ƙĘ&n0U-Z'Up$&RYl< V:\HA=Hg$IKݓn^ $t'yzhxh% -ϖҔ@s* #lyѭt$1)8sj$:GBkSVR:?Ħ"қq9J±$WݤrA>.4u[bx#S鴮v]׻~+`+=,b2},d#+@d,fo 5}b&4,hC+rXhSh,kc+-ms݆@-o+,qVv$r+Rֽ.vr.x+񒷼y^!7iƃP*".EII~@կOZ^~ %`W2FpC6tڑ^,S[ xatY=Ȉ]nv':MQN4<2_WL!>%^+`NV]3o>/GԿ>|k>/o??Ï_o;˗?k[п@. 6B`5 *Vvz v00 ,,@-.^  B _@:   ,B,^@/Ђ҂/|!-=` ` .F f_~a~,`B Z!Fr!za ",!6.2b#FN% !"1(`Qab#R"҂",^-a.", )b@) u*a>>/ B-3.(l@ 19# ';:`/Ȣ=^>ڡ?//P7F`;`BA#> t@DD^EE@- G *dH:HB$6ZddLdB=_CCI#E%FcPҤA^8^dZJ#w `QfqS.%v{~08bN'i6tB\}a#vRg2&:"ed-uNhImkc[^#hyBb߉!+^ |Lʨbhh%#o(~ )Qgm&1>id~)i gAi 2(@ +nJ,lF,jb ʂNj^j +a*0$*w>fxi-$k:k.܂.f a%&"Fٮ@"۶6(݊h֭_Jk-m lBpBr&.߲%-sinzZ.ڒn_dzn,nn~jj nKj.n ,^f+/ ,n#2BiG0fpr-hnɊc;,e + h j Ȟ / .B Nj .@n5qB" cq4hFCd/x/ܭ@šboad-٬6Bf,qhFz1`K ñ!@"$bprGr2n rpf&/rˢhZq𙱦2Jf#hܪ - 0Q-16{rڟr"_4KsQZ4_5k3'_7{s`s67s3c:s"<>s?O> 9s?#t?+>34>;t=CBbϴaua?vVGd{ue5e_Tgfug5gRhvƴuivk5iQvX6mmNnkuoOoY0/vpvr+rovs;s{vtGtSwuup q#wv5oOwxGx?wy7y/wz'zw{Kwj;6êw~7~{~4w'8x/3(8GxK4KOxtW8;83878^8x[8߸c8۸4Ӹx׸ 9ǸxSxk8'9g7y8k9G9/9w;y999繜Oy9wx9﹛99[y|7}c7xSz[Pwoknsm{:mlf40@wO:v[vvv4w4i0\4vvwz4OLGek{esd{ca`;`\[;\c;]{w;C6c{^]I$~7勹w;;~>䏄䳾+~c>3z>B>~~s{>;~G??~5=7>o޾+W? ?k~?c?:L<@8`A&TaÃF8bE-fԸQ"FAdɎ&QT8Re˔,] Sf͎pԹss!94m&TiSLF}(ӪWBU֦]&N:8Y۶FǒWnKu_zWoI}g*_K&nhpFƍ+>trMɕ^luΞ?L|vq輦MjF]Pꁭ]xGخooMs퍹Q7-<4;#훣r»͖fyꓯCxϧSzyѧW}{Ǘ?~}׿ P ,LPl!P ) ⓶A QI,QLQEQkWQiqQe R!,#bfA'R)9G+/ SLf3LS5l| ߎc Kb:Vqy_ #.N °Qf{Za a &/,7'F"ёx4ųM !E!acxƜWDh yp6hACS_ͅXDfS#ϮZ6~o !`Ժ)#@rRu>$N9!gl5׆p#G>ʆd$Yɯ_<7RT Ӧ"p7p/ ]NjpOF"(q_A0%79퓯DZsg-Rqs.:ρtE7ёt/>>pӡODUձuoVzv=_'ў]mw{v}tݱwzw~ٺ x#^:yC^-y7 O<=xЇ'}#_]BtkOD@/.!&xdPvXxn>@n!8nD}Ox*.c@vAN/t|@ Ok E0OP0 n ͰlnL.^^ n_bpP}} 0 s1옰pp /d^lNy ]qL nqn:/N_~h w1+onRGt6}  PR Iad@d`UtQsqqqrQ 1R=!Q"!r )2!'! 1 #Q#2$"I#/2%72:%M!Kr%#R$ $%i!9R%cr$a%e"sr!g'(u2l}  &_(+r%a0dA'dA,'Rr+'2#.k%ݧ1ߑ/["q..=r.R)&-+.(,R17|'nP4O5Y5Uq qnPNpclp37/q\װ Ŏ n8-+kB 3Ts;<3rql*.8 t7=_>sS?q<<D.?1yCX\4?C)PCCC22>yD{*}̯nb=N*YEo09xfzRtGYb4O ACJ|4 !P'UF4uS9S['aTeRVQu''jVylWWU!zXCWm,IX5Z9JYkuJZUUUw '5\3j[oU'd!Vu]%\O]uZݕ^1j^^uu_U:ƃ"C0 0/V//.V."v/7$.22c763:<>cCbU:vb"=bVb.bdIfJfWffgBgYC g"('[ !e j VgcR((_g{jkKk kQ+v+0BN@ bfz JFw "bbbbviwx.R+lookj7e" &h 6nHaV@BN@sVk W*>w06t pip6 "~Bz,Gw2 l a+h z%\zBsJ~,mڪE+fv"w`-d[Y B:[ [cz-B]F,]^EN$xdJc$- gXƄB$+5i{b~m*hr¸)keR@[vtN&9B{a,b,cI}u&uM T pbfu{g~Co+JŒBȵ{n'ϼKEwa %\B8<|D|' *ēj2\':BJi`\j,!JMFmB<^K5n Dvު Y0d黦[\- #Ϩlʣ!/͝R|:Gߜ+ Gٜd ,/gv7<[,muTG!}`'E\[y$b`'j & z\w+\rmy։,qյg = \ط'}}(3,!g '$w]رu|(dlxuݯ}V~Vl{Z] HNrJfk-]t*$;>1',ƺ_ O% Z-\] ~ 'ש͆/DzbK"p&įFZ&|ὦֶֈpbjc~A\啨7m^h/ήB'ȿ[?ֈ%d D-zJI_l ,mF dH'N?d}miDWf+Ӎ^\{?Lir/D[>YZFZܚߧ" DyA.,?'͓?'Diui_D <0… Bĉ+ZD0ō;Zңȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} Wfqڽ7޽| 8 >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;ݼ{ <ċ?<̛;=ԫ[=ܻ{>˛?>ۻ?ۿ?`C s, .H^8x= `j`HC~ Hbs7%"p~b΃-Hm/M:ڋH B9ډԑS!,     !""""!"#%'((((((((( %#   "##### $ % & & ' ' ( ) ,155666678 <%?&A(D(D(E(E(E(E(F'F'F'G'F'F(F)G+H0H5J7J7J6J3G /D,@';0(" #(+.01221112222211#",$(*(S)-.00112111111121212121222222222222C,a~"hs hr fp hr$hs-it2mx4x6788;>@F{Vjc}cdw`c_[\ZZZZZZYXYWTXTPURPTQPSPPRNPOMQINWEPZCTZn#in.wænzj;Q7]$ㄦ+ͻߤC{L\轄3sMۡmfaت .5BӗOÛ?XM9I˿N/b-u7j}ey 6Aʩ'ewkk9fX$f^""G߁"n8P}h8XuJzyɧᆝhߐ(rIc\ixהz`)dVͽgvjW,ޅw"mf7a%]sU☈&oiחv/x]6$ac3vjO~X")"s1ꪬ:<"ݎ*)ϱסCAvjar숾&,N8 b&%\gEΘ-{u;w?.+䖫tfCXmH vmE koD鮴G4l 7[G,jgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:D.2)R.Vi&.L>G#AP2=(Ї%lh6 !XcMbYC>Y wǡcjD" F(<$(=яAY>Ԉ\d#iGR6”DeP.IMzԇ/ 2ʁ\=$XF0 D3L$&pX"(k94cPAT1.Ag%Yr!dAAc8 .| |A < G$?O"emDBЈ6ԓ5DQ[(D&~A.T }hD2ҒRF(G9RWg?jW3LRөY5ԡdL b LP gIM01MݼTAMH>" *XԤ%渉^]oz5u }]0X5<*f[mc ٢`@2d8Q LhVs mTZՠ6"`=];{ִ"%x[׸3-QIb C@ۈ| )gA6[:T>kܖvBnsܡ E/y{^ݖlox1^K\W h\{߂p`umk` e҃Kرn#Tp.֍=Ĥ>ְ|BUur)N'"h7EV3,3c\ckAg-L3 1dlj"(9DIbǝrT 64}h)^㊙YH&yEf 4#E<#XL "jDW|m=3^TÍh5QtJ8&HhW סU@a6>vF+٫VjY+gkص-U Nܶ&v5CIb atE>tq`B^cx6UbHчHчQւHnkR3Spu5ETr'(Mz([}E@}!n@xu'nD燍XX]]Ud@Z9G`UHeȊncڨRxrFsXa8KxxX`؏K_,GEӈakא ׀{'8$  H 8h j Y?1(*ؒ9@ȑ&,L"dMeovrFfUFw&0eA/O!u8HYȇ`xHµ^fh&C8.4Rm'X#nH~gii8MLŗ`YzhyHt xX鏘8,gs{sd5u0d ڈ~?q88Fo(/ᆊ)j鈖i 1~鈋mI︅iz9.~wsl%vwwl؆Q(霕^]Y S+9Yy9" δV A}Y8\׉:jtzjjʡ#xZZz(** #Ia8@B:DZHJNPR:TZV MzZ\ڥ[^b:W dzhzflڦepRrZpJvhzڧeUʧJ ~zQ*IJZjT:zʩڧZ꧗Zz*aI* `H J ] KϠ  KZ ` j jȪH P YʫOzɚ*ZIH* j: FKQ0[QغH Aۤv  `P*;P: p@+ zڱ` [ ڠ Q}HJ =K Y  ֊€i I  @^cfIQжoqsy`+dkNQ۸Q @붙+t۹+j˶˹๎a[g{`PpHz  T[` +KQ J = e {Bںi~ثܛjX H NjIZ ۻk, ۺMZNzkDJHÀ K$ |¼`P < |&(Iڷf=,3ύ=MMj$zm-m`*, F#I10@ҮK">J  ɍH. ¶.ƾ؞sFݞ:˳ ;zߟF~n J ΥSj  o ?{ajOd &(K,/ߥ.35"17=o?OACE;/IOKGQ_SUWY[_]Fb:dv( >:#ul??o[gs>dt w~Woo~_|FnvL='96)QD-^ĘF=~H%94RJ+]< S̊-iޜ)&}6mUp&Y ѷilښ1T!7i8Sڴc#]~uJTyD VmAuW#\ukśn^p]d TįLp=IV&Ǟ8v`e5(MQZvvliӴ}[fn/y^W'0.Q)Hf]VӥF6`m'ykU>&ro\EJ/D"z.:k lF/2 uF\!B$E$ćPlq]d(DL+}̑+{QDq!g1YT(H!F"LR0t2K&ĒG/ M-iJ1$sJ3cD+9sP@L b-ߑzDXHKߐ  K,d@~D:d69;b+Ɯr.=:ޤ]ݦ}¨ }CY|zXaZf*TftzO(nFV{,$}pjp:x'xG>ygy矇>z駧z>{{?|'|G?}g}z~?D%D`@6Ё (VЂ`5Avy>@<8BЄ'Da UXhh/a e8Cp uCЇ?WшGDbe+cb8E*VъWbE.vы_c8F2ьgDcոF6эoc8G:юw#ʣ rzҐ/|+C)W 0)]T>E2ґ\%$J6^)g!̱!M׃L N,M4 nPCָkKtf`'E,W&ΕjU/Qܚ"bEO}Ͻ՟Bd'7`1V>t׾c:Q͖Iy~;_FfvoM|ۓBg1\A#F섹0WF-T{_idWx헛Juwc+;W| qchV'E棠q2D+51kf6kϏ$|f:wsg>t=hBЇFt]à(¢%=iJ3t5]ÜuEF?: FuUBOկ5PXַnD(‚k^Z{=lbFvlf7φv=mjWt 9o0ۀNBl'?o#B|j$lmZʮ8h!,4Urldue$/MLP}KOSo0B} 3t_$^I ˈn-MTsİmsxќ&=Mxތ0gY;6gye De/Xl>5L8y®/ rE+u. ݼK=rSwůjv;Af5hR>8ᠪ$_Wf[GzpmЃ"ľv7(8WJY׷eӤph{}6F4+hGr!B>Jw' yJvt2wS `:sZ,UnsX,;#!H( ;t[)7/C1 k?>{n:=+I0zt@c1ě0 3k;$8S8w;2I2" 3K? @ ķF؇9 9WO(!1' & +É3o[6 6t9:;<=>?@D3 !`ADDzFSHD{%FKN쩘OQqRDT4+TWEWZ[\]^_`d( b4blcTedftghFfLikljnoFnq$qrDt GudvtstxdGxz$Gz| G‰F|Fȁ|F$ȃTFDȅdȃtȇȈTNO|ȍG1zbz0DFȎTxdɖlǗIuəDǚ{$vNxa\gІmc$JIb|7a脃:FJeLʣ$F0lȭOfLXN,Jbn؆gh։G$FKR`J$Hn4mLL}DŔFna FWHćKcddf LIȔ(L8bLc(LbhMFOXMaȇ\FLbLP؜(fcܬHKQ˼tzl,HNj OwLHIM$QQQntFPf\PcdbbXQW(5MQb,llQ F0h7#O$bQ(]I¹UNQPP0Fbhib`S7%5mxS69-F<;>? ?ӿA <=FxS:UTAeT=%E%T@uT@DTHm԰OTIP-= LTD%M-d\UU TTT5G-I=\UKT]eU^]ETYa_UaTCmSS9SghgVbTc VdU_5VTO }L ==W]cMVn-wWcRORb4ׄrPuO|nQVW=MF)J:r5FatRk(etNxXKUb2RS5OO ONNNҴ-FkMϥYcFJ\FtMIŇlt=F(x'mZ)ɑQ˔%ћ rY,O ` N6w ` ϵPVf<Ra;Ly0FHY n6H$=Q mD%m[ Z*6pa-ay;.1&263F4V5f6*K : ;~8cch8? B C& =أAdx丈d 1(yHEdNN.s-ɂTbL=ЉcP ! OXe@ 8ʸ(p 쀝^dZ[TH Yґepf>^ `Rse)IfuJnE"m&nc{峚S+abs&4i ِ1j2p Co oi:RtihiCAiifi鏮:aivn锞iDiif~i>׳-B+j>1ca΂.Ꞿnq>~WviDFAjF굎h뻾hNk,^Vgwag^lo{)8.I߁qpIlxi{>|,8蘙֙i4sH)mymt7a9!Ǔ(πeel˹hdG)ndn^oNond~>n)omoFvWgpJ k P$ 4Rb pAcmZYLyagq>axqo`X5u"9nu|; 2B\O]u'j"B:#t;kuW1;:@*c:k_v1w%SOg2<2ċ;Qwf5мqRvwډ(y+/fj~&P`=- ۪woЃ5C0n,y:,xc=ܲ+d^}YYW/,׭Mp>WzP(X󈉅@廬>C-rX{J|)l}330{a|z: P(vijXyy|ӷ)@Zxp} <r!,(X0,1 l)#dHH 4A[@S710~&ZPF@Tw~w:kKk „d5iST vl5dTP#Ȑ"G,PQʝKC1I)eܸ5HmhQJ2m)ԨRRj*֬Zrs5v-{뾰c6M23 4P!iݻ+ើsfͶ8"O]9V;1޻&MO7ʤ5زgӮm6ܺ vv‡/n8ʗ3o9ҧSn:ڷs;Ǔ/o<׳o=ӯo>M\ 8 xU3= J8!@3z!!h)"-R((8#5xcy26!=#A '9 y$I*dS!dNLJ9%UZy%Yj%]z%a9&ey&i&m&j$Bu8y^%J96R[3 k"8q,R6:4M?4N'uSGm]CN-\TY#O*7">| 2#6WSv|K&(eK7و[ AݹFZ._\r^kΕLlNnҜe"8&&3Ils@O&{ķӾ9{;oT1 UCk=^/g{* ] wW1F<a'Qq=u{"(Rmܩ"q,R^th>p)cƺq0"M"Dűv1x=L091><$"E2|$$#)IR2+p$&3Fh$(C)Q)SURl%,c)KSr-k]򒖽%0C`,&2oyd2K!23i _آe419j^%qp'9iNtӚl8IMx=ɔQ#H?7 _L"/0["C. j=x PF14)e #hAK. -$tIbaPL!NҢ6/.ւe&*gaСr%)JUZыf/L5StdDlaP[ࢪll$&Ԗ:k'+{YLզd Km9/9ρ΂E.L \\v/`21.+MJ:6vlc>3(󙿝Y Ci;p(Fv]>+(FwgdQS{MF_;yԸnAzoTߝc pP<_thqe7ĝ*kW58;эd:r,sٮTUrAݱ7Mlc1N}^OX~8U|Җ5[q[x'#ìPa;ɸӽw~fK]+]~uKh=x<1Oyюˉrvݙ|#__<3qssgwGwU=Y=oI9o뽷~Is>?x?nuYy&ud?=Ŀ?\w}=T c ``!MʩmaUi+o \be2`^>.]'a&5i %`]2 a8%'!'J!r\`'ai5C`05R~[ !X!A-tٱA]amagy+&a& a=o5bަ,<ֺ{TV$.U)U&{!-aUUa%Th""'a+~lu,Sy ]h(-b]0bg-O%$⫥SA*'5c/ƒV!}u%&>c/jb=XUݡӑ`:#r5JFrRoL$'P!6Ñ d۵kETX=Cu$FrHVI"C|/ޔ"#UJJGJFK%YLѤMdZd:b$BmOPZQ$RrTMRSdVA/HFR%E $VVVWڔS>?S#X&Yzh!L$B V\ @ejQΡYQ,0 n8Sa\(ifRgg&(yhb(i jeifkfl*le:G,&onocp’og'g"g*lަl*'iB'4IgtRsZubnj'6qgvjsvwnxgyvsgzzf{{f|~&S}'~g#''(~(&.h(>(Fh:VV(^nr~(f(((.ŀ((¨hҨ(⨎(h)~(ƨ&r>}A0g)^)fh(~h(iRT}i.i)機h(j"jh*JRZbj*rjzꚂj2BhrjjbZjRJj*i}6꣢*B߭j+ҝ'" F+kBh;V#kV1?mN+11ӆm1g@lqwGlC,q1q 17.- #r!",#ol$# ˱$o![q1 3m'')2(S$Gm-+(2-%m*/*+'0  s0C4p3+ p G338s5p9R}($('5W/53@0¯.4D?Z57srEFktbtR4VJ4KK4H0@#33ôMߴ~}NONO5Q(BQ/5SG`SGTc:+T_5V36[uV5X DMY5ZZ5[[5\ǵ\5]׵]5^k..^[gWH@HBDH./,$pFD$EBYY6rJB Lc?vB d'e+fi0..܄BALj#<(up6;aw܂.@.lB/vD؁fہ X@k4s;wBDtuvug$@H}/@h'D{7{7Bh@hwKjq{sAq.s@|v+Gx#/<6S8u'ąg7B7_I;s8'[,0nyn7wuGD1Dċ'}JyBrߧs)8k79rH(w88S0h8B,7`S&PD.O}AB\}2y~pqD9.LWۧd zB1ا#k z~"vJ~2#kz~^OGU۹z$v|?@(@{8gx#zDs-7Jψ~o(Y=c;6BmjWvDX@20|G~FD^:~{qϾDb6dr>Hؾ3O0>wys~{<|w+1뷾W>>=s{r/ws?K7@j4`#;aڱ30L F8bE00PF9z,2G$1T4:J䃋 /^pEf]7#r1P\miSOF:jUWfպiG^QzxR$A\kLdCE;>@qIP3% %jQa΅zv%rGUu+TP8 Fwu)V$A6aQaǖ=vm۷qfkcǏΝRoquF p1Π35(X5F%;Es? Y3@:Ǚ}\2@(Vӈ eT& k!P `S04e,PC/pÂ:DG#)(9Jp?P?]4 ?(i@ $Ёi1DO4'?l 1 &\I )z*[57S9+{ˤkPl=Gz:t/3O+ ȱ Rŀ\o ZElEndӡF-MMqU]y̭_4ϳ*u=/]r9L#>N U%N"f5#Fd-]+Y@ VPi$33'i¥[DO']D-( ;^X VT`u`KN>v'9CEވ~KR@ep%Vbs >qYgusɞA衉.裑NZ饙n駡Zꩩ꫱Z뭹[.N[n;TDHܮλmW"(\ /i6\oq5JÕ1\9sكI/\'bP]iz yw}uCO^y܄W}硏^ܚ|^{g}9Gkho_뗟@4ȿ& dA *p xA V0`;A*p}_GP- ]P5 mC 9L!}B qC$"K8`%0wD"Qb-xE,NP[pv|h_n] E  oؾkc;O\cxGE/없]1 + Ge|(a ṃ"[HTP`+]IX}9-XL/z80i1>.ӋE70gbS͜'[~Etkr3 l#zr}5k S ,?A/(?>؂~%1C: P"jQԢIkpэ"4)9ъԤ<{O H+Q3XP Vs%xT!UUaUQ"Vue 2ff+ U~m# Yz)kAYr%mm-+?oC{vq[Ҷm-qV̭.iE\NmsmZەmF񪹬CwbֶZtx͋]>׻Mm?3N &"߼7uKUc!:N} J8*_CZ߯&~"/M\į`A~N}d* )V^q},)4 g2Mye<2 ehQy_YRasB&3Bl<t?g r}lQNl̿DtXs?3܃y%sc0!yckWz`$xi^B։|ی f${?" ~_MZ9#'Јb_)Xcn쮵 㡼GoE__o |~4O+ og)ķqM[Ǔ ||sI>kk)Yg1aps=g5"T@7ё=bhuO]kHNuoI[Qe7  '"qhw.'d=usÀ x2BWx/w!yO1yl7AzяWXQzկga{λ^i{|?7|图1OO?ϧ}~ݷ_{3D /o~{l/PJ:pG+68/ hA4dQ% `D'r. oMpP0o]`0Eoir4BlG.Z 4B!4!Hog>Bw4"t@> w  pP LFQ$0GbHb O u#T cP0#vb 80## ݰ#4<<Y'GB0#$dCB I^Zqb ;oDj{\txq}1sqqQ#qq{QQqcQñqqׂqqEJdr! "'2 B/\$DR" A"C$\"qcE#&GPIDId!$qy#W1!?#2($RUhq**U+R+[+;+/"4 @ B,) D& < n"@\r@/S. 0 :b5"Uz+,@p[0 >0p") oD4KO3SW35s^3G&A}d^r1u&b_#0e406=PbS5S9M:9o/s0S/s;8Ss8s<351<<;==;;s?ד>?S35o@ @ @tb]A!4y~aK"B1w~2C=rΉ@st12`LtE&EcEY8>4SREaT`p8\tG_E}Gc.#tH4I>e@I4JfAƉGJ4KtKKK4LtLɴLL4M4z̯iڔitiTi4ipwaOOt$\Dj.]Bf!j"(PA$Add  Q]e""_tR-uBT2l!"6USQA'A*BN5U bU x*"TcUfB0Q4U^D"Rau YUUXUubE_u |rV&axOVB@e"`Wy"_b[A@=^4"N#& tj< l!Z~\YU$$bR BMRMpA`c` Ua#BaRL $`+` cVN8vUc"HVcaS6bu.Fc'eiTMNQhvSl6a#c bh![db"7P(cyPd%BX6!C B u8k B hDLpj6RR5nP$@LE/5LҖV֖0ږB.l#gn]M/vTVm56o'avLu ZPcp#T. Wn CpriõeVXrY't kk 0\L{ BU~Vv juF?YGT%"qy%;L$eHB%F~S#PX &[-mfbxwGC@V;…,߶9%$@@+ cle @G|1c%HAxBT!rJ! 7w0Z-;?##.HT6$x]b7si&e|K6",!f x; P8rw#e )o3V,y 09ocדd0qyW5VX fi%L<^͂PjHTPeZV:>peĐ 1QnVYِU%%[ œC9Zye'bQ9݄k™VDBZZ(&\H7-Afq"(V♜[LƞicbPPu]7S#n7%8R eq¦sud~z9F%3f?I]z^#P3Ʃ;!V#K檁Qc5d@KZc8:}%B_tf8ݣetS$úX^cW!mhh"h&h*{h.hZyAA:;IocM;U{.P{]a{i-`j;u[**%v;"zHt[+#;jQ`;``;Iqj(X`(;`JػrSĺC;{W A;{<| 6{g`}DBJĘ>B ~-A^t#d+LXQ8#X0I]6%~ѫk ]7A½k=F[!#}ǃ^a#}zVMm}6y^SG۹Ł鿼}>IWǛڵ=/G:fe  ۝#Dtɟŵ<ҏW}]7}K[g|aԹPk:c=qڧ?}=9|(_aWWWW.8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ {μž;O;EY̛;Ox\9g[=6-oѧW>y>>O>%GO?s!,w)))((''%$    """""# # % & %#!!!#'+/3555548 >!!@!!A!!A!!A"B%F'J'K'J'G'F'F!(E")F!)F'E'E'E'E'E'E'E'E'E'E'E'E)D*A+@-?.?1B5J8P;T=T?T!AW%C\)B^/>Y5:T68Q46P52K61E7/>80730/02(13!479>@GELJP!JN&KK-QE8T@>XCB^BJ[CPTMRQQSQQSOQQMPOOOQOKULIZOM]PNbTRfWSjZTkZWiZY`YYZYZYYZYYZYY[XY]TY_QY_RM\^5Zt/Yz-\*b1j3o5s6v:y?}H{Yxiuutys|r~nriv}eh~cabc_j^ldnmptnyfZK?q{0jt&is#is hr hrfocm^m L%:-2121212121212121212162N5x9; ^x^ BqӦ^M!yRl [=X&T Au ~&X!J>'bHE[PA`D5 Tf|R4d* 9: (Y5" 8pI 魸 a2QwU*#lP;Tl]6WYb╮v+k覫+k *_N GpZ\-NXpEL%o sI'Zx*,`C-d^XR3J2E~HD@,B:,ԐKl^);tICdݥmT[lo{r-+ dJ mf.x[*C'vou͸䎟ύ?Lx: yxQ/zȮҮP*D;K?߶\MB!D"IsԵG.Ĺ[Nw=mN^qA1g^^~uQ;G5 HL:'H#x4x' bm~ @f9416t{P(f<;Kx I]ς2R+lf%DJdLa)NZ5̎ڴ[C|8[fMw{ӀSe&c&K0:U]rHCnS-rCeP+G9qDIU?m=d 1)oYj\zNndzz t;ΰݰ:IJv5H(Ք)c"Fby05Z5/yKxZ6(:e3K=m:[(teK>H Υ.wi 袄tKZͮvz xK^>elb Dio;7Pߠ~7l11{\po0' ~/1܇w(;aX6 M@puQ8-,= dyD&0&,XD)GL 65ȑSd$(LtC M4LpTsn(,%,xC2,.9?=md͆ : >Wΐ͎;|ɖ1 h_ZL& FNh:\FF3MkZuF _&:#V5\nZL*}eLt#m]L[(վvP]mCݾu\\֞5kd9GAzh}}~7No?S<Nw\׸` w.(>rg L8$0f̡7(h=5z}^<2'/9%wLƳ E.K)J܁`%_֯nrI@P;KU);wݿ;ǕY@.P0%.HdoArd Xz? ?AN?řD ?>xvigs3Z=sZ=uǀ dQ=pkrgwm'pGJGpՇ'a"G@M@u(\#\E\3'5\8vA?h=/(8xr7VX+p8&8FpLwt8"qhUxpqG|Yp6؅Y|SqdqKpUvH+wt|w|y*8!ȅ(8ugHx؇X,GGW芡؋èwz'~،xW8|!TvX&Xx7os،(_h8(X,!Xhx*~wWxHؐD1[8ڸX|(븐㨑x[B&sB)Ʉ(1K%ÍHI}98 V EIEELGMɔKIG$OJQPW(F4 IWYGrJhHjYTtN!U cɖg2K HSILKr]wxzY2{im?4YnI]It(yJYieuɘ}H)gٙy ɛٛui$^$VޤMजĜل, )Iωc9@=i@9@89?yIrٞNo4cE(tAhesAsXsAQۙ4}Yu[3d7v$Uv&ʠj3:c}~UG=5gRwLáF)wuE{{{lV0guy1V;zIZEq9? yXt''VG*\W}SʥUPY؀gʦikmZ5bڐ)ӑAH1ꒅ*KEx:ZzW2Pd D3e+IDKZal!B"Ɣi~ɚqlUe٫zj*a^ŪNJˊj[7ZZભ̚bǭJ"嚭ڮʮGsdJjZ^њ^PsM,щ& ˰ MkթRM{۱ ";$j[Q; q; %t F"&uQ"IYZ*-+;ŗnvaGKMO4UQ~ @<>,D|6lH3\"L( R\!LVZ ^`U,# N?^>pHH JNPR>T^OVZ\X`bn_>f~f^hlNmpjts^x~w|NF>{i@i偎^IG>~>咎n|}n~~qn`HoaGni i@G^~H^ fQ J~Xp~州g@^ɾH'M帮d Ȯ~NJ庎ЌĮ ^^> J  ~N(L&p';ck0GNkF%P_5i&.X`P `/b$`hoNpNH~WVfhOhOpH>G^|o~?j.鑞P/tokH.mOq䗟}?fPG~ /ZIH~//I>BcC穎h O?`jf/MN `V~A >Q"D]HyFFU!Њ@KNDReʊ3rH OęSeŁiJ9rfÑmԙT)C@ GCt֜-2ȑj2.i5 mQ6}C؍_={ցjH $fǸeX^%Z3'R#uOfΝ97+B5hMuqe|/=-j]ÖMPQDXHCG6Ǒmjnϯ֔NL1q xؕs?)F ?ȶI=;c5Lή{< ;?hjCq'c>)V7H4J0#Q?|z 8c##"L#T$MX)ҴK7x,!2J&3մ6̲Fn.ӿ+trM?݄&k駨|̼A E21tP-,l-R$N=hUA7TΠH?Tǃp!W Ke'2B NR*!\Q -P KW_ 5[XY8v\s!F7rYgD_]pMwY\B-[2=+Eq%Z3U+bNbK4v!rd:Ԉ6~c]~yNY,9>he9V0RU!2z'|N<ڦ)V0 v 0j8'!n_9s `р"<3J!x0ឈy-z%YGp>G(+hI<4w1Om?tb~tS&=9Ir$OIUl #9HX^畵.sK]/ $Ks14fD?Z&+t[iK5Mm:0fdfl:uNi7)p3˜O~Sh@:PԠE@P6ԡmB!:QV4hF5Qnԣ(HE:Rv'IQR/85sPԦ MuZМԧOէE5NT./uSAOq g P03R=W:Ruf5jYѺQni[JQTe}]XⴰM=,L X.%}JـN6x]^za,I-Z6=+j=:ZҲ6=,f;É6TjsݶomEaK^6=.Nez -$Jgut]fwڵk]"-Kuxы}7n~KWە/|OvE| nylB8V0[ wp{##^//|x$>p%c טVA<`xq9lc&1,dK0C$J~rwl$rf%xs7[ǒfN,c܉n,*vy3pY6?p[Af=tE7v Js"ma3MߵӞtcC-QCMP]e'[V,[MlhuǦm+U۲^=o.7}p[u=t7 a ЁE8T :,d 4h h@2s@@E)/ g( .p\}>!Oຠ 3 >6vA\g*Rzؽ˔s.ډ6L$?yoA34}e0w[W},'[*#Vk>Rn2ya,o5r,|~GF>eӟqm(~~_?࿾o/|_7bw{<~"#@;= ;kk1˰v} 6c;kC7ѻ''*ӧĺ;3=3:;t;@A44:}9}*2`:R}򀬓#'HJ4[< A B0\Ӣ}4@"܈' )|)9d7i0+-<-=̷"84>(}C? 5B9D? $>>2LI$I\Il? tɉɊȏܾ IT@ɚɗ?ɋ IHkI1T?ɍd@}\ '6GsJyJ|ݢJOAӹ HZ4i\jDk }@4+LK`l˄ G{KK՚K_TKLLı$L,HKɌL<=t}LLK<<Ŀ (d4-l,Ҝķ$GKƯMT\ߜLL K35N36^31E^`vԝ;cG60b]^?NcA^c8~E9cv&'\GNM>.)]@n^BncL~M&DfGLdɵ\RdQnEU6Μ\)>e}^}"B4Z("̿N4"THeg[RΓ+L Omf[x[\OlO З9 OCƿ+ ೤9}ۧ]g}+x5ZM~37h"Pѫ :P'Q5U.= ~\~U4vX~h2M}zBX3iX5*;ҕ v/0MYn^E5YpҺTrDCm%@(_߰`êin Y;ckck5B6<[=nLtf*fetNDdYj^*Hy `r}l a h]abM J"l l!vUОցkeMlD <.2kklϞaVEVs~jV@@oa>;b;Ea/LV'\}~r韦5=}b~9j X&l>kyhNFG[(%¦٦cڢn oEm p%<WpYEBσo%V[Sƭ&?*۫v: [[q%m-^3<5[nv֜]S%B\ 7g9\EZ~RWnX>kF*jټJ^p-s=se7I_h4c95ͅi"ABLodDdWcBW,rV.ld߬2%JgVXteO,B;l!bDܾmqvfU^n.іwsvmk/bf{vڮ}gtvz~u\uH+uox?*3~_cs7rd7sGs_NlIx'ssgRTNxu]yI_xN>vtusY/]y_cC/zyJ%/y?yOvGs'omze {{/{8{O{zOy{{&&`Ry76 AO{&ş&ƿ$b ˗$|iWW⸞ɢ _9J }‚DP̑٫q|g d~c*~b:~aJ_j 0YH'#i1h ?rdAr ́}?%%,h „ !Ĉ'JtH"ƌZę3 Ӑ!]Xreˍ2vr`U:UH$JX ⤨J:JfCc4TZr@oƶ1δj׮6cǷrK\x.FOJHgN:*#.<,&1pBx j+'*[,ណWRy5]wk5&@b! 3 9&Cn6sҩk.OoWgߝy/_~ǿP?G~' v { w"݃2HN!W"wb'xcRb%`+%xfFّ(#,#Aaܐ;)m+ABd6>y&}ZA>$A%IvnM7iBo!PƉ)ʐ`:z֣ԨQZieb:؎6_A0mjP崓l 1IDR6ګ}JjE!ںլyE(ӺU}$dDK[ΦJPHW ZP5>%F+d$DRinpVa*lW_]_ m3AFS ŀY1Es\@B f"*P)\"xb@L**$Ni+)rJ0Wy1DWcvM_3%\a=B$znlFVVms5qsEٌؐ-9'n9+K֜cǠs,zƤ_l:ŨGz/:î~;;N ?<_<+;C?=W=K=o=>̻|뻯{_{ۿ@˟߿@ pӜ.8 D?̞/= /?(>/$T Oh-Y#(u)T!nC0=,Ȼ ~E;%u.l! ^77ؐ"m]-e!!;Ra(&E蝑uT"Q}D!]7vٞHB2v%'iIH^2<$;LbRrMIJO$TXr#mJM|,a L]b0}IW H2{i63Tf5=Qr<4Mc\%8It><%4YT<)OTsDӫ[Bҏ5l?Vծ|+c>;E,ejY9F֯=f3Cˊ=-iQumjcYv,L+m [ZVC`݆HS B-a=rW\p4!RD  {]jX/LY}CjxY6+@Q ]@P$n\_uU@Afjը}C04&l;4 i10N xP tpEޯ;_%x#|/ ¥qlc+.,]r¬Aً֜!I y2?#p^Tt&hqiQ<trg24$1:hAz䳝9?Ζ(\hMڡN/yuc)S;r촼p9vO2-NY̶VE#MϚܵ]`sd+Z@E"y)>qU {ԢeGX{vqXTiH˵`7\+C \pq/SK00%7V 2Չ+o2A35]2 *8[9NHBֶyn[2̭ٻ^8_>ao-zo|dViZ>VT ]DzUTBwW+ȳ{V~s=e :PhW:ٙӝx\׉ޣ{_7͏⿞7>/mF ^g_ɜ@~Gz쩖fGqoa!}iFeO՗B;~'S__>o>D>sv_?Ya.4=]M`Um}TM@!DQ>~] ~Q ҏq`ka ՙܭڭ !5 ]2 ֝ZQn"^m ^fV:aaץe!oa ɡa bpaM9MdvrZȝWz3a0'`xQx&{&raMѠɹZ&*N0vi*pׁbx"z< A@-.c.4jfW4N&Ag0Vjj\>+µ\RZjR%&βrW+D5RA'c<"=#%\ɉ&ī.),XZcѸBXb8${OĪZa)Dz/樋:'td2aƎJ#V`FѼ,blNϪ̶vP!)&Q-" "kXv߅֪:lچ-!B--m-mޞ-(- >sZI. An )MUƭݖm&ݒ# G͛*dV^mN.RM Y"m!*2i ԚQmbVo _=('i:i26(a2iv/oh*(..ozZ^hp/ro>Ǘ^.TT\"LJpaZp c kp n.Z #d sp ۑ/p ).nq"*q2q0۰_n9zV1f1nqv>璥 1ˮGܶqp"r%Y^ Cn!K/("80W9^ƵA1 CvC6NFO"Oʪ+B>|r&؂IvdfckOjTynڲ/+ݭoʦ|u^iIvrg2_/֯p /300==Ss/3AW(N4Y=;o5)ꄑ'%~:p/>G'pvD}233ױ'ali/GpQ1o|}2t&ڤC"s^P &t'Ӳ:UXȎaPl,ZBI"l>Ҧg+nUqJO111ϱTr[Sq^[[r1-tU]73a5b4U^q_#R1c6fcϵ`Cf+sf'vgsvatcO%A f+gqdd_MקTFujlm0(wn;n ?nqpsp;wtk I7YB+mwrtwxwZupszr&N+w?@94wy3t>/?}GpN,4/!~w<{8osws;8@#E4!O%`kviy"/3vw we#.us~h;v7/[CyKy7-fl#je8{xoCah 99jj9"繞99::'/:7?:GO:W)ozW`s:낧::o:C4Ǻ:#P4D''|(:o(2 1Vñ(:;Wx;P{+C{1(1oDkE&'t38;(3<óCz1lCV ;)(c.;W;{;C:(2V(w#¿{{VD绣W|sk( 1l<<;3V8<ӻO|=OVL1@4h4@4@;kE(<3@ ==10||W_W<~VD_~WBěo><臂o'G>ؗK14lCCg4 ;>w((dכ?@8`A&T0!1RC{p0 ^b'%8j@RȐeK  bOI2tX]&6 dw KO]iϠXhҦ,O bαaX]*5Ek}wnҺ(K_?tLԨ\Żsgϟ;C @c4VrW%VHAֽ(b'v=ARE 7o`ʤ MWK@P=$N=gt{wΊM^;*|3gC&k j{л (2iA"@ 0Cɸ+/c *ѿQyѧ$hcrH3F!OKCE6R˺[;ek ʏ0Éʕ&2Hbe哜>9=+f8,Lکa-Y=lOOI5URHGγ*SR>+L-tR\UTR$:4PY=ҁ^SZ?Iqr]?_'kK3tVOfsQph2 9%Վ$6[Yu:I=,WqsRPPOlaT:5}G4&6PxN# :Hdp]̋me-#0juFhΞrfHמUZ|1 6ꁰ6H|YmNaP3H' Zj8Yv*Wbn8Hٻo$Pi"BbܓbRdVe(˻n tIiMw a0Hrj]b/DwVrS\[|=9ەϲQwˣُiIo7#]Rtnǹ+.3 hᗈ4Gs=Q=hn¯= 1(">)Aa2*QH$h2 Dg*Bx1KKU9{DXPdc!;~h$iB qDe$4D ꢇ"H* ZL D)#5z1@ e2֋MHC2ĔLB}^H0c``$(pH1r.)Ye+JXRW%+mKKa|/La41Le.t3MiNմ5Mmn7Nq49љNutg: ws .MS?rƓ!= RЃt E@8$pC!QndhCJѐ&4)F;/ HKaSt(HiВT3EO>REC㹀(U HRsJL*H{jӚ3@EVQ+5z p<@ P U+0\ zk] W5v%+gXJu,DՑtAhm ՂnUMq\Dmq\.us]Nյu]nw^bS ,X?i+^L@Dm}ߗ~`ӫ"B4P N.dKCfuǕSny`S{%l_MLWu-qY|q+c wx\汸M"60AQ {b 򁋜$׷z\,g{8٪7qj3F1os\CwqρNczM:\Uӏ~u[}[וu|F^{uP_عv=S7NENw]m{w_<~߻#xC^w3o=-tēgo; 8]ܫ{=.>xF#MNOS{Ү'{jM znyoV! 0jK/&*P/ $9JI03-CpVPG0ipmp]"koYPԂd0^P~pa LN pӮ\4P { O*N QA tK$Q` `Hp J1;|Na P!aq1P ː PLa j L(:@^  "Aii q qv I1g LrP񠰑-N9pƐe2 La l  Q '2!!#> "c#L=!I2$IbnIqnT2'5qQa j(j$q@$M * @2%%2):) + *%e NPr`/LO./20Y/0/S1/SS0)/2;19:Ϧ02*)#}bR+20M*5 @555!_32k4&.g@338 M :X33:Jh&sU7 = + ))&)-# 2>2$>Rp %j+0qa1 cp '"7 J>RR6*2H@>BH$MT$FE oEq} p%  b%ֳv2תtth h^ Wc-05ǴLSLJT$m'LAN N߁>a#%/TC ,U 6R?US%5R!U'sQwR3WuRYUUaJuP[TkTGWKU=qVapW3WyX4TTYQouX5XUK5ٶL˦QWXYZUZkrU\5ߢڵ1*Õ]ߕSUU2Aˤ!YcUVa_UVJ3S<5o}CGΆK( ,UX# #9|GxbcA`o r7,X81"eX)8|w~L\z7(M󂯲3T@AՈwuko؈M4X7O;Сv3vj3kiqeVӑEve#V+GD9֓1o*4}ט$QXruVhyV9hWjk7!6 Ӈ3AA8OΚSnN>k9$GgXb-+VYEFy3H `HHҁWN[߭}wEc?HOkt%bz0{ŔL3#Mԡ9nYJL4Lԡ2: P;QIQuyq]z4eWrqzr:5y:wtu}ϩOztugl%؋:!XקZ:Z ]xךۚyCڮi71W{r4..4ڰx\A[ptZ[wbU[+/ڲڴX&_I[y[y1x4;Tl@jS9Cӗwi-Y{ٸwdyYuY_;G{[[ֻUyrj B:{QE:Y^;{L ٷ"mB黬'] ɷ؉yԅ0A\i̅ \5;?^=A/M9C~x B|q>Ч^ϥɛ]Ʌړ \ɳ@~oS^~՞ݞ^ܹ7~^l՗> 6(]OG37;I$L>?yK?W >_OqH_},w??%x ? yyDɻ ??_[ytv-5.XB'vCu"ȇQdʓ%5Zd9eȖ*_H3gLu&̔>- hНCiҙQh/Ɣ)F0+:y$IYf"SI2J*՟ b*-׳ʌ}alړl]ۖ[EC6keJof5{₃>Ej%?vL.hueƬ+= E0z>u =ԫ[n/ܻ{߮䯇/>}ۻ7ϣc{R>tIsm!In﹧_Gdރ>Qqga ֈ$?L(^q" C(z&(z.28cوw1ct} cHYd0C7]Y4I_aZK6)k%u;ve_FL >saAWnZZ#sJ4 b:h_j㡈Ψ/WǤ)Vb;hYI19uI:*R)(S0[x))bvҺ,6޳ж' d޸v;޷,qʎ rصF;/{/[p- .˺p pvX[l0k,0{/|/N,*ʺ s2Ls6ߌ/s>@Mt9 mtJ'N?}4ROMsT_-Xo\}4^<6fG}v?v0v-wtםx_7b2&,b=2_SG.MKg9ӝD.:΃*M E=٤߽sU u-6_&B:?9[o9O;ͽc?~ꭣ4,x}?S?ѝ/}K\:񾗭 2P!d)(Zp q; F KA ʎ& +2"Ġ kB!]B h!*1 \b1N!sXEF" E+R(ġ#rl#1W<#h5zq0F:ю|!H? w## iF<&r0D$/@ܤ& 1eћ^Fwʢpw]+kŲt`*s9< 5:3MChj;>ToInfT_G!ʷ3 [I'GЀ~TIҗnMŋESPJwSe *IJQRM)̀SL[TƂljeT͞S ԰~0 `c *;TQѲ2SD;ܱr-zԭ }HT&ELl%LȮxTaF TP"asS( <8Xd+b V*v ˆ0CWA1sT`]h~)(H 8VmmJw^ט%*J\P $I8"ŌnQrp$ 1IPfp$p# IJ. #I<;CquDpLbKVS8qOaC{x_  >/\+=KzY6_࠾RV" Ey*&YטTY:g*ֻͭ{ MN22H6*rh \.z٭n+ЮnU4t* `*C*ޕye8@-\, 4ʪlN YǪ9FcujVII FZ'Lzn ׿::>i96։T?NhvwhgS۪0/ZPsOUf:ɍJo*}s||On4+*j< vo]gܛ 7U 4Cnp||!'X>/x:#>[t:M5o壴y;a>o)䡯[㨟żԩnc==:cd/r 벐 f/}y:ɛeO};~]ǝ.޼Ox_!x(~w|%xyƍ/vykmѳ>f}]9%'9m䳽 0ിnC:p{yo{TNw~Q?s\>1=Kor?-rfw{Hp5d~ |vn7}c ׀X}8|Ё 8iQU3sm-(3/xOł35X4mчsW2d@|65@+Vńhu PWw\80,*h\mVO Q8xiWd@ qmR3vEeZZi,ӄO6P 2rXu\{Ws5% ϵLlzqHÖS` r 3  3W0,_ ':# ]2- EVk\c@ '2Y+i`26Z{\2Y(ix2HZՌ[ugUu%Z(eȉߘi2hȌ_Y+ v\((,Y3ZS`E 2p *s 8Z iZ2$:sS26i]22QgH**I,Y XikuV0Pe1ٓʖYA/ym'(i( hE6\(lK3%+TfTiVp+Ø-qIRfYjJ﨑^*,_]iSL*CYi(TOi3`*Y*d3[\z&yWxbJ5P2gy.Q5i0)eT2IyWx锟Y w90cz}d)iٗ2Ӝ7&4))3*aYi^GIezI)f7xya)hu& 'Z !Dt& V|^pU+Yg2* O*3zƍ0C֤%:*1å29UJaR2_Jbhg`ʒmf*zd33J9N|`Mkz2$Kik?Ij*# I VZZYfEiZzYVWڪi}jhhf*3i:J3iKnZiژI{IVu93hVw\J jFt<$ 9u74kgA@lWXaik^1klǴ0HK[l)LPHg{g8iXj\رȲhq+\2Ƴ2l R 2:P2JU_uY{E9_˃7h㧃Ghe~=?ȵ !8(Gݓ涀#xj[۸x[˷ ˸v95SyIb@ZqWHkt۵&)3];3cQ#9J{dS44û{r[6۽`Gwʻ:v0*E梨Ǿ;\'꾤w70ꙿBDK,cjډ+Zyx\lz,I,EDüJ*J,<}~Xgk0l8ܶ:|t.,KǻڷhqOǽk;+᫷HNKVl&Ş>U,%(ŗK1K<׹K9dfi \sh¯\JȜb]ڲvkČ~EzUt+\-y5}^J_YͳvȜ! <{K9l wД,J,yz]Wٗ$'{τ+CS֟-q ېvĝ8uݲSG]^[W߯޳4,@5]79  mG1 ~ y ] -/:^M1.-~:ӷ]m@.MgջD=WY䵼GjUN܆?\W}[=m[krQ~S旭B=|pq,_^a>~m1Q2Q~鐞>^'t-(>n1^&3//-' ~}^^p++q+<@pR![ Sh_W^^n>*Ġ*.,)M&fNo)l2$ &<8BN\<@O.o즠+tY'q" c $!jZ\ pQ0%F+h3OMb-E?&[&<l/ӢRe@Bm  t@_P&Q~qko-bB)?$'p N)!(dOk2Б-!&K'1/6^"(atQOO<`o'j|HoYQ "R!Sqq P% "_<$(: E`I E5^ԵG!Ep$S6|qF'eUM#O4SL@C!胨•+hqL +K#jˇ޽k71یBy5J0,ӂddIel%\PU޳~Ӯm[2I/[6T_&B0 ˥{|[[a:SePu}IsgЁ¾xa%2v]]c[w!{~+1,5! |"p?0;" w[7 0@RtB7,Q,ItD_\}/E4(5ܱ a$2=' "U˜BR jD!Ly+T04MlQ>;3-d)}P LtMHHJ[R ,*QD#5RJEBçP8tS Ȕ ߞnY3ˁNf1l͕ uI.%crVa%HcEVٙUv؋ekݕ, \]\seM9R1zV~xwVr]0X&YCBqyaLU<HUW(%a!m㏶ <I ST3ĖmI慘%X5SJgyfgyh:o/jNfarFig&{f(ꩩ 8楻n:zh9l7b;|YX)7Lq&m:9eaVF򎂬#!h8"buAHFR|$-yILfRd'=yJ~R$e)EJSR$*YJXҕe-[iK\Re/mK_f1[NӘ2]BSf59LkNل6Lo~Ә'. I~a@'%)Nrg.Y_Ӛg,aT)NI hAMP&E9Q&3Fy紤B%QZ)=)K|t2)MaRt$IiӀUB*QUS"5J)SAT4 } !pXUۓUIn]jXʞ+0+Wzd+$*׸l[Z׾կ|` +ؽڕkYX,dXղsl[~6dEX͞Vkli)XӲ-mQ U]\[k⶷lSVrWt5s Z2}.XK׻ /v+^b-WI*֨NK>4oFkR4|/$J4pT\k4&\ ӑG4 sKlg2]q?[L8Ƙ|7I4Ǔq={8:2}"El!#R瓩lxL27/c2ce;kVsl7us\;kv|Q%9uslh9sk0ŃуJҰ;JCyЛ;b$c I"ED2X#`:R׻u}k`[&v}ld'[fvlhG[Ӧv}mlg[vmp[&w}nt[fwnx[w}o|[wo\'x ~p'\ gxpG\x-~qg\x=q\#'yM~r\+gy]r\3ym~s\;y}s]C'zэ~t']KgzӝtG]Szխ~ug][z׽u]c'{~v]kg{v]s{~w]{{w^?LA7y;~D^'|-m 3@y]Cyқ~g}]l͏~Ǔu_5>6R&GQ?}g_O[CK?k@Ļғhs?|@@3<#c>{ TAԿ~ !,'''''%#"   !"""# $ % & ' + /25653,)&# !$%&& %'3#&:!&>&B)E,I*G(F(F(F(F(E(E(E(E(E'F'F'F'F'E(E"*C'-?+/7/052/712?/4H-6M)7O#;S!@W#A[%B`,Bd8>d;d<>eSJKROSPNVQMXSOWVWQX^OZ\VZZY[[Ze^\oa^{ac_i]lZn[m[lZm[lbtj|qwyhj_TIEA???=;=?@<~=yCuBs?o{7kv-is$fp fp fp!gq+fr/ay.<161212122222222222222@@ù䷴ӴhXQQOMJGFHRh̚V͠NȨLǻGCBAA@@@@@@ H*\ȰÇ#JHŋ3jȱǏ C~< ɓ(S\ɲ˗0cʜI͛8C$gΟ@ JѣH*]ʴ O lJիXjʵ׎P*|ٳhӪ]˶-S:֭ݻx˷A9劕:W]+^̸cd k0Ttk̹+ѡ䁃 ]Z5װc˞mEC:f槥uCMȓ+[x\;78yӗOy2DtIS.<0}NR~'Ș~Ff]AhD\%$ןA6h$hWvNwT]j]kSvŹ&>NxDi$P!ݏGaTb$ 6Ěe1(5wdiF9U~ݘ!pȡWFvM)Ty栄j< "Nh=Z\V䓕i]o*eyw;eIFy1&*n릻H;t"i3ZzwZ kFdS_Xނ첳f쒸'F9en@H6d.'+dW.o73:U2 gܮŘ~ ,2l([i)ˡ{.4lݬ~ IBL"YHA2򑐌$!IZdd%3Nrr (59R<*KU|,5GEr|-sKD/I <&2yeⲙΔ%49_1J&6A@@ 2#0rns2ӜT:ى_L K=Nr> @Ю5(BZP0gD<0O~3Y@!QFbGAю~+!5)IEєr\qHeҒt(LuZST Au z$zS K$ AԦZXͪVծRXJֲ5gMZתְp+@*׺ڕt^:мz+`WmEb .i5,d'{PRsŬf+Գ,h+ ~-`K>(1Ure-n+p[}lX!YA9A-@׸-kcYNB`pmӋX7=}^v+аt'0?@uS;PW*(gx vnCiۀJ oUX v1?;c X L&0o<V*>:ئorolO\nÂ'ЇyFF2ʩ,h =y؇PB%2Kӕm4Qҋt!0]\$m%SG4ҥl7 N 䣨4_VN1} Z5-b ԣN}j^:شv53jcG֟um`kۧ&-k[{ε=}^{F7yFv~76]ny;Ӿ']owqsy&3=̃#Ṯ >ru;%Qzo=T ޤ1sb;tw.'0q 4a[xG_(D: $ն'o<=g'pva=-@[>A:@M ,Y+pߟ\ʾ62Q6o;V'r'. 7urz-e?{la{^=-ƞ|7?o؟/>qv_7~GkYwnOޫ? 9է꿇8WN=$FE;MC5 C4TCuCUߔ "8$X&x(+s3Q1cA]U]5X@^G6c9>]A]=@B8E{tU/h"|E~pXU8mpp^GHa@ :`c8v~p&&WdE}|fh VCO/(bYȅQA5 EawȈ`V18 ]gሱM\!d vF&If؈y 8hX x&V(^8OHHX!qٸH8U qQgۈXPxP qu6gu`fqؐH8_aAf[ PHxp4qqGqwqwy0'GE6gUgCn=R?7]7PCNW^r(T3e}HΘxqȖhRזI䧑au):6Y{U1W&`a|PhwYjyxwyeN脡yZ Q]\hd+@WpVPQŒ)8zWjxCᜱziwW^TDW )9q% Wc~Q蕩- ڠ A:DjZ O ġQ՞$j@& 渢%ڢ S 2:4 !;@=ʠ c4>ZKNH@JZixO*@QZtX\ڥ^`Tqx1:@U\g]؄;87CH F5X()A 7eR@T(5*!9yEJvѭf  \K ڰ_(_ wWUȫٚ1XTfʭX9ny+ɚyٗnbW:Zp }8+ ]*(9y:Iz+ {hJKmѽ;[{蛾 (+4?[@$K@k@a׿T4]0/h|ə<,ΝlLZLIûlljT] HX}<}<,˃#ϵl ‚tϏT ~$eG0@"=Ml:l)I\--/1~a͆L~=a 8: #ӽYY%7pԉ4̱WN< f'c<rR0Fyז}- ؞ׅMدv ׈kG]|}،}}֑=َٕ؞؁٢m٣mڅ٘ڔ]ڠ-ؐڭۧڵت}ےڴڸڝ #-ۿ-HumRzmR=sMڽm۾ۂ=\! l@ }րTݩ ڭۜ-k=4PЮ5M SNJWYDˋ 0b?^^Gv\ăd⵼ͷ \l^Hu<Lo^#!N M[HU.ыH~NDJ}HEM#lz4M›uL GK\D>Hl`0lnHn>Hǎ.L$>tnnNM۞؞K~~LNn.Ą)~^?N㤿/?8@:Xsba T"Z#h?D>T40CS(.02?~Xwԧ'~DE%|cѱkJU·<{P\Ew ׄإ]%U4uG$02YYv?` PHPW7VNaN}wo^%vbDx{[OZ:cgdZd7jX{ ޔTVVAl_o5 wGEucffzqhdx'p"o$.Wr k3rW-'s l/wooOԏ?/?o_Q_,H BLk>ȊD~f@'cƊ !0zh9ƒ 49f͆ m:đEG6,4VB2CA} *O[iԱ5*)ٵYbϫ]Rٲz2-ӹX -b' q$ʐYfΝ=YbhҥM.=j֭Cv[vlسm~=aChrw+0'a2qv]:գCtc `bמ4zAw3|Mҷo4  |>P8b2S)>"= +,@dXdJ4:S̐E WtDELxh Az)Ɩ`4r6$I4d B''ҽ%DJ,2=;Kִ2##lHJ5[|8S:oK-O>#3K6 r3Q=:8%}R,%T4C]NTPuR/TIôUW_5VYgV[o5W]wW_6Xa%XcE6YeeYg6ZiZk6[mAqlD<\sE7]ue]w߅7Y)pB7_}_8jqD`F8afau2Hda/8c7_BHdOF9eW>w^\f9fgfo& .4!ӑڰx `A X%AU2%.Oy<,]PB`D#dKi:%*a$0LD4La'Hh@Ϝ.IqM}ޓA rM7]ٹ"YH)@ҹȂDIQ( ad \R}l$  y":uߌd#/*tO#Q@4IeZzȡ1IO-5jw biY~N@j'BP$HNw׎%z:.kXծ.\ (BBzCBV4 2ƶp-B8p1S,cH{ׁ`Y!1H$^\Gk']o$g6dkt1vq뾴LW<ב7v\bq%ңBw9 0" /4%sgR 3azn-; p%?<DB,D9DCCFTDH=BthȄhFE{`<A5JO/}QQ=\BlN Q Q R̈́K#P/(R\&mR]KImD$)*L܄4P+}O0LD 5}Nt׬L9CՄ\tA-K+T,pCmGvA9-NAFmGAll:\l[n$J~'Enl&ξZk>~Alll׶l؞l>mҶklܦøNmmߖlBmfllAnlkW!_n&6oVfoNo7pn.g^p~ p pgq'oWyHn*h`F?qqq !g"7r@DnDF$7)r-Wp/.s2Lx(p4q83ss;:s9s8,n*6@/sDsEsFrGrH;sJqKWA/tC'uQ'P_QROS7UGuuVuSujXuWou)t^u\v[\_?bGvcfvf/v`vbevevimZvepiVo[Gwsu^O7'wxg`wU?rv{w~wq^Wv7xgxzw_ujv'|gxm_dxOxoxGxx7qx7yOy_t6t7+y*$y#! /z?OLptLoz?rztzrzVYqg@g`{o{^z{{{{{|{/| ?|2O^|o| Gǧqʯ|̏zs|η Op'r|GW֏p/_!~J/9~gD 'O~~mG(_c)Wo_/wI~hp \!Ĉ'NtB16xBG,i$ʔ*WN%ėʄ/ 'РB )EuiPNBR{׎GUlƳYvز'#۸xҁwmk߿u&ȉ'S TCm܋z-.q]ۗi7uW=;gWq;l\}$䁙6'm^<ǓGW͙1v'Y={|k?&vSٕ|nvssZם6uV`ơo]k"Vf-&8?18#4"{:o5#/ Rױfbi_C2Hy2@9%VF9e]ɥXv)$E_JYY9&Ak [朿yY)&z&'v} pYh>Z`6jRJfY> 5V:m2i$PeJhZ*:l &qתY%!r&$ew*n$FXAlVnK,DG<5Ewcɒm} XM<ٖiYVV8mҭ0A^ln/k%Cfx.)Ǯ$5E3Xv/aB>/<3зut1aO!,v82R8HH5ܺȑJ,*N֢`Zk3릧2AR޹E]7d]C F#~׸+oS~vc9{9衋>:饛~:ꩫ:뭻:>;~;;; ?<<+囯Rߟ>>)xL&?9+p(f2|`<AH 3x>Iz C(&L!UND {=b @=N15CmHH;!Ft|dQBTQW|(.z!t,cO lS`]# ڠt`< 8ȋ)TDAcBN@ ["Z(@9 mc!('"JRJI/Q A(@)[ xC| ` n `IL $8I7&3/T!NJ= HVL )#tq'< "JwT"Q?DQ2 Lq n=ЇrAkFYvoGRR݃HIO:PT-)P Б%'U)ISat8AgVJU=u4-glQQ3)P̥۠]}JI)b}HY{pV9tϬ@Z jh` kX%X ?)+CJ9NX!#MRD'>Z oe b%Ņo^hO+]U]mAM6 RanH]|n7"-hx*W$IRh-|;L9sZzN;?JXEG9QjFd'4a~)",gXG:*4b!F>2%3N~2,)SV0_@X2MF7f>i nhv f<9t,A0@#4mBZю~4#-ISҖ43MsӞ4CM, έݩiGp"C  m;B@ps8Te6Xa>ESMT"L^Ӷ:P)Ll#"2ť2䭭l>f+ցCXxMl23 $woIebޭ4ن~ا7rrTЄqQ(Nyv7-flifn ϋﴼWpO}C' :Cd獩.ΥR59 jC٪VdhBN^7aA;юG$B'zS8Eb[a W%_J9G X*'crŮ>򊻍7YV*33׿kk`z/W>E&Hr~vp9oGq'>:_w_ @ *Ͷ~=/]HpziWWryЩ]`Y]΍\KuuӁ!\]߳q%Q9]X?CpV s ±9) ImR!"! \Ϊ)a)븡!!֡!!!  AP "" 'x/D%"$FH"J%^b7a"'bb$ Bu(!(b "+a|*,"-֢-".."//&O001#2&2.#363>c2#4N#5V5R4^6n#7Bc6v#8#8~89z9:.#9;c;#@2?$A@$BA&$A(()0$?.D#EV=^$F#J@$d4@C&FbF;J#K9$L4>)D)!#-1R=B2:#;t^RΤB#Q.PN1%R#V6SʤSc0>>B*(2>+)<2n0^:C:JD8#@e^>WW6`^#af5&bc0fON!+%H+D1AS;\A0Wd6d.`f#ggfg~fW.fF6eRA(X&2B*c*%lf0md>BI$^%nʣb^g3fvcw#w~'2#!2T )(jlA0nch~iR+2g{{||ڧx#y4{'0AsV]&c&d[>%쉊x(f&**>B*)ꢒ~j&jjjjJj(P eL%(zi|0J1*ª^kV}'OCCj~_B4:+B+nVꦦ)V+ҫRkZkk>e#")h#bFg&t]~u.i7,&0h, ;&cԧ4,hl,*r#yg5'}0Xi]c&P'$%ǚxRwZmwbvjuJco Nc~B1,ҥNf0,4NhKr-nJ#e|m2-MZ0m0m,JFGHIRnZ`bnjaC#)B(t(HQihT⃌.㲦Cin-vn0كo"cj%g:t2]ngZ#."f @>nmFc>B2c>c dFchppwlw&pf+pG0:["wDe "x d }"A 0 U0 pEB0M5Z'qeB(1WY 7Y_1go1w1N[11q1DZqѱ11 !!'r "73#GC$_2[2&oq'&2'(c2)$2*;r(*+*"U)r)Dz.2/ 2003112'7كj\ٚdeQKC0233S2""!28r::G7s3<<3(s"7W2 )uO*ut s=3t87 2t0S/[ /4FFFw4%G)k Uu3A14>SruMOH7NLoOctP[PS HO1!MTP _X2:tYTB Wa_.ΘHَ^#Yb(cY>m:X7z8buV:0p 2g鄤9)U<}׼>WeUV!"ahGEa^Gy&D(((% nP-Vl& }/ѹ\?7uKOuYw=cأ[4-o9Wsm_U2S?Hʾ0/zpuu>_i[B-! bT:7\ 2E0w`is@  d ؖE1A nAB%4 QB-t aCΐ5 qC=D!ElaD%.Mt/N pE-n]}ye4C)V5|8 ƑcA<1}ch6NibS!H=ePxS#7HIɐ-$%CjҒ'8_4)Q 2WT+aKY H,H4t@K]/La41Le.t3MiNմ5ȱd7WN3DɐH ƣIgX}'iOG@ZysB Ps*ެ'7/,;Q7a]7>4UHIݐlDTٮO'Bz O8-^:T'H!qUmbUF՝*Yc rR$!Z]Oi5UN .9btA qN K%ם&Em`?U+ra!حTl_;ٌY,* "|%Q`JN%pС^W5Qg;ղŬnk,yBN pOfV*s]+Eq]}( ڱv௭O]d{KD9dY:q{#99_`$_8L~{`җ &H; 0NnA lU&IGKʡ5&Mkx;!kLw65xQ1kcN4^.{Ԋ浔[rhٖAUrE )f-ip4د5m?:yOD&ۚf$j.kUيRlc?OYeL3V5$OBg[';306kV!g><>UM#:&54ǥ(-:`MFmqA+zКG]iO^ð%6 =pp!Dѝnuvoyϛo}7\g7gfP!w!d311nx#prIv )lP"'y)|rK7-'R]@pVy͟{s\;ˁ 9E7ˇ^/x;0OUձuo]vA0~_mwwϝn_{w=w]7| x/~wxO oyo~%> E;.zқhGUzAY_=M_{^yoI}Ѐ~Zf(l?#ڎui:܁=pg_+.~} P:@N`/Ư̮/0֎ B!/-잠%H!/p@IM0PH\ CA1wApήw.ߎ o Oa ̎VY͎RRώ z@P !N!.lCAN#PQR/'Q-51oCOAg+ َ_1pQQy1|C B ^a(԰nH.qOOqێ;Y22Am XI! NN2"pW R"Q!!ӎn$K"MR#AR#َ%%a&cqJ:n2$@'A'/.('[2"KGq#)q"&*|r+!$K. R 8&k#,+@ 1320'2Gp,@.Ѯ01r1o1s p <(3,,op/3S1*iF0C!12QW3 SErI*6/ B7W7R'Q9!3,/oZ#92.eq#y0t;݁ A ` b= Ў> B> >s>?3<0 53/>3@!@A#? 37{s2s810B5NtE)4CWwC4Ms PAKE%TE[EGST:C͓BFsvGOtB/IG3@2=/]JI}4J4LtCQ I tDsLʹGNHtJDMOP'PSP4OOQtO3"ڴSDOsQQEBS)$22! ӎ)?I\/S#UNA62VK4 psWU%@Wy`-.XW9Xu*UX'7(uWWYq8Zo){*!8\ ]U:O[N)ӎ^1U-EJMo1 DK0 D2a'bbUu%ca#FdO8 A33e9$d)RfI6L,'bqg{e6f`ffi5251pe+/a6du'G]hcUl_!ObRaU6h^K3D12-̓^!6ϳ[5 pOK O(ֶpܡsq!r7U1.o1pvo<t'Fr1wX5qj9u!7BwvKwXvo/XW;9%W @b4wwc"͖pwm#z=AnInW^O}ޗ}W~~/7w^Xx]؀3O# x ҂ $W3W鎃'؄#X؅׃Kk؆gWw·Pi8xOH. "~DH@JQyHRRӺ ۰̓|ͣO$CC;DKGZz.$* 6 [ULZ "c{qJ+ɴX{]{w dU;]lNp[n*z[䷡{yۺ۸ 9+ y;Û[s{[*J۷{;k{ [;۾ B(, \ǻId#ø*;QbHB/|'<_U$29xZȻȭ<(D*DDTD=x$88Ajܦ˼Ӽל߼*(Z*`A*FLHz 4;˭'p\Iѽ_Z!%v%0z.v磧M\5n%}!keaKZZݙi]OS]=G}х={͙BP҉]٩٥=٧=ա]wجڝڽ}ۿ}ɽ]R==#0A*0a=*!R. b8]*$%~S&>] 5A>E~IMQ>U~Y]a^ΚcSBba 7! RpN@L N}0UFx wr~ɾ>~پ>~>~? ?!?%)-1?59=A?E1h@ H?dT?Zj_uhw?!?wh?uҠ~?m&( !,''''()*+,+*)('&&'''&&   # % $ ######"""""%)-/135566677<"B%D'F'F'F(F(F(F(F(E(F(F(F(F'G'I'K*J)G*A*< *3%)(&)"(,%+/'.3'16#6;:@>ECJFMFL!DG)CC/B@3CD8DG=?CC7>I09M+8P(:S)>\*@b/>b6>eDDeLJ^TKSXLMVLPSQUVVXYYZeW_tZf{\gag}d]}hWxgQjhIch@ck4gp$fp fp gq$is,mx3x::<;=?DIFDCBBAA@??????<=32222222222221212121212-Z,il,Zu/Xx3Wz7X|8Y~6^5d6f6i4m5n6o9s{?ADFILNOQQRRRSXkݒ˺] H*\ȰÇ#JHŋ3jȱǏ C8aɓ(S\ɲ˗0cʜIM%o|XrBN?w JѣH*]ʴ@A4fPʵׯ`ÊKhNŨBkʝKݻxAn7a_Z*ʳo~oǐ#KLrY3%㉘ & QgͮM^˰c˞M6ЭbVt}qd=0©~[0湡6_9iسkν{F|'(^|yj|7^Aҋ-?sW?/<[z݁&&p75h ۃꇚm(_aEj>y1(4hR,cq&b{U!b*'ߋG@dB"5y"pw\v`jT!V!ɣam^*ȣobxfr(h)蠄:E> a^ O=qXkvh駠%VB9C ֱhj뭸 qY䫢ɩJ+h&haV:S6kfK#v+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z /g&t W0 gB8̡qh@aH"qFL&:H|)ZUfq`HFh\F h|#(9v"aA0AlH"Ȃx WHH⒑@H0 $…:b .VyʣT&;Ir)e+KT+f$ DCriJ_"[XyFZsA I H  H C2d , 89r3Yg;OӠ̀p!CRZ8+y| 5H7sMod!'KIf9$Y0H=RW,Kc:S9uQ]s5"5MTp fA Ԥ'})J@x*A9<+.j֔Ra[:նY$ܹ־MUAUj ӄlYq` ^ njZ¶p >(Q,̶V lKAKѦs(!i-`Zsuli$HpBNV5 O0-&mAZRʎ?K Wz*i\ϺҖԟw^:Nkb5<ukڠ>%]$\Hޤ*A+^0)B |&[a+!atd WbRa2A\ңMcca E …3%#aP ,Hw 2 Yfqi|Crfk<@5t3۬9 PvZ P!șgP 'tjg$j E9ѢtZK?[vWmHi-Zј"9jR+!\-q-FsUMZt[k.`Hv!Ac+{FgO;fMMJؾvnjj'5QUӟ~!.٩=kWݭD7 &_;4'N[3c4;l+@\hH]IOnrc e3 b:.i JQʜ˪dG5dY:93,|^Nk36':}XZ@ P؞P*NB k'hBi kԹAKU]0x?8Οؼ!*`׷/."y5~CbAJ`3 6=sxx ?]'׷}(- ɸt}Zy/{Wu'wouWwrsk9` ZnV<.Ԧ_'qYz7[f[UZ5/lpn*90jeV7ce^T{"`\y8yx)wy%T^Ey%wM^`6`rIP+aN1SU`&!ār7y>xaQ{`fPga"zxTpu9vd4dPeee~\szFd( KKP6~Ttc\<>tf{r #Wx#qr%YXEkj Gip҉j k@\3ƈ舞jkoog׋&o릉oÆF Xr ok^i Xpn⍺IaBlmІmfKFhDnyB r_Mฉh8Eox. 9$u8ȍG8ؒ6/bQ6Iq(?AiCiC9.X؍R98XZ\Y2`` bYfyhjlٖgInr9t)pYxzw~~ٗ9ɗyy٘ɗYY9)y陬ٚYi i_ncɘșY)_WieYTa Չי_iPbɝ)i2f8牞g0hf`Ii`T 2mm_Iu:)$`9_ei@.\`\4j8Zw,-j 0*fY4U`U iٞhigg`e2p_I[ <@N  *'ژ&ڦ n*Iv*=fjٞ-js_)Z)ʧ~*yI 3*Mdse` IЪn`d KyJ JxzZ=zIzfP*tɺ<_*c}s`n )bj^`2P*:a@ʯ;:`iQڰ[  ۯ ۱{{[ %۲$ #{*[/K+k" 4_>+@.8=?;˲K[I;MCKDB˴Y۵R^{ڧY:XzκN+\{b[:՚4궅ʳu;cKw+)\+ 鷖ZJ BMIYi 8՞ԢL4bמ=~؂=]s؈]g< Ix]hrroxK?LAs^XguXOP4y`vU<l@'xy(L'SWyqyM"ܕz{v|wS7Bq6^M0 ZZ7wB5!6YPX*#H<ы(H\G`NI臽kΈK\d{d C>^~!E|G{ONE!G >IT&E)F( Wa4C6~<[<>C>1BEE>ED~hg^0JGSDI^)T']*G$8EZB>dCWoq^s>uw~y^{2~NUl;?<:74~nnN\@Y..F㡞C⥾⧞n>>Gqo0ہ^B3ձƯH~lNɎ ˊ*f>nB.k>.^0~׾>_n~ OoN1n&O.O>Vގ).ym"3:/9_^MOGQ᣾UDWY[ Apo~CnfF5mjq/HSoG}t_r$>ovEgFF_t?qT㌟?OG2_5H/?f $'Aa}or}t.P04gs tV/GbKt~.xoKHLR3K_joj j#Q.rs?tkWW DPB 0#I2_ ib\b0s8:DRʅnvXpu"f$GAfX,fN}tÇ9\UTU^ŚUV]~ 4fv`4b("O,@7yC MOK/4SM7ʂ/m< >I=SP-P q3P4P]Q UJ-菷 HM( tP4l5 DSy-(_[޸XGhH1'lwXORN!E+/S gKH%`F}:0<#BX҅]Ą"F.J,T +mwc䊰 FE`c7f:6s 2fk@,:N8jjr:-/&/Rh9#3내hɄr- ;1 A H_,%CL.Hl GkG v;!ĝ9,؂0Q$EHAvo]O4r{8ugzKw7('`DH=-nO^%* X kwI7^O`8@ p .Ё`%8A VЂ`5AvЃaE8BЄ'Da UBЅ/a e8CRD5aF$t6a\&DB> щ/=ъWDD&*UF#8FFDlոƩy^\ňJюV3#oxG>Sn|co݄Qz*b"G2![p$#%9I `d!AKfRd&7ٽPbreVOҔ()O~ӟh@:PԠEhBc@$>WC%:Q AJ6}}Yps7yusӓ?zЁ GGzҕt7Dwzԥ>uKUzֵo_z>vsgG{Şv}mws}%L{?NH;> x> g|_ys|Yҏ= PR(wLh'`c@#ь?}}>E~PE).q辈5 "~nwu(\O,{~ ??#? c>?=۾ ,@U8K@+L ,?> Կ@= :A4Eۿl$ANpOЄ3:T{@_@hH#B>c >@D9D(=3DEh,N)M9M0a>pc`%:ß+9dD;P?ĢC?DXO4+P{DS9TDD>D{EY?U@E]\=H;`FMLcECʳf RC=>DV\EL,CC8;N0B$L;%F9Nhe<>Fs`LulǟECǰ;CX0[CEC>¢#Gc_ Fcapz9GȊGeȐFKȅdBQH[ɢŠ{ɘ,I @ŖIII,k Jdâ||Cɟ\JJEHHMLK/H[GxeKmCH\J ȣ[Jddž|H7EELŠ# N Mgd,:M+MMՔN+hMdNtN+Lg|LULThK|G4դJkT :O# "L”ȓNf>Bइuu? uL P5M>͔8PfGP̠[NU|eQE-R"QN4[ N :A#R RRE)ҦQ+:,P$#/u0G2uR<:)>5R--AuPD,Q8,eT$]9}TIQ0<>&7OwK>OGBT:%AeF}S]HEUHTYT7@#NUɲ|BN͟VtLBQA%:rȟ;tuv=:?Wٓך8,XsEףw-:?ب4l=&UXo}X ] ŋXVWRM֚EY[b$ЕM0Q+CiD6L( ]dI;>ˢ DZ NLJڟse9e+E0E%CzEJLHZZe􅧭Lڰ6-#՜ Ne[-Z>[>ۣRe[ܶ4ܹZUܻ0[ǘO\?-\I}ÃOٲG:CFG[OhZdݨK@]H=;uC[?c^> fR?} ,g>5_K>_,?]_=>($û[:ģ>Ϥb*6;+T,:)b.n:/P0V%(V+>c[c36>I(.c8;:޺7cc>c>=3.C4+d;^d:nd8~65dCNdE2c.,dD9eR.e6>eTNedf XXvY[\]^e\_a&b`6dVeedfggvhjnfklinfnpfprner6ufufwewyey{f{y}~~u.&hkf rHXD&hhmfhjge&>g`@YFb>fh6tiqf&|Fg8|>j~l6ꨎh橶jjj~jW~`jYk>kN^nk~밶꫞kdk6k|P|lY&l6VP^.^ɆnlƎil;ЮXll&.mXf^mlmVNm׎mmܦlNZߖflֶ.Vnlv~mNl&nޮfnmnlʞmFmo~oop6on'n>ngpl^n긶i iGi/iiihhWkiqqqqqqqx^  #'V^rn~kr_rx~e^r,sx.s3?\r|^{nFg/Os5s`[~s~zYs.s.G@wjC?tCFDEggH:'FtIFNtO8/GuRSWRMfQTGuSYZ_;Ohy8CjZXv`sVgc/j\Gg~0~pqdfjja/vUvlueWiDvmvntr?wstoO Y luOskjt_OO}_e~wgwpwO'o wx6oxxoxGo_gFygwygw/wjOToyy yyX>_m'rt|unquy/zw3 G#G蹷{/skJwu:0G|n+htƏ¿)|7|;|}O}_*|b.}dӨOR% eA  8$ߺ *vlYb46Y$K\wGW7߽zUo\oFƉe/^f@ϑ6 Yrңb5׃GZ4jȸINlysٱcw|oީm]o:RgP{9uc_=kw.8ߟ_;}fs1G`zgUQuKaeT"Oq!S#U@"O+/~$"P?0?(L?ԐN Y$GG"YԒ$6yOR9h9\ 9.Q\?QfCcg&Tp`֙ѕ&yLt駜)hi虈(y' 1ZRVMf䦍F*9jUSni*ꋰ(kJ5HL*ү; ձ:,J;-Z{-j-z-;.{.骻..;/n؛./ L30 +0 )I;KIŤdR/@ IX35 ,F)"$Y%A1U\&;s(\"q&!! 5{3@c 2LK<5Yk5S%b$% M 囯5 ~#6Z_RX,c>+=%6i~#η3 PI@ @d?ԥD( ŀ| v@'1J* :t_# q8!c^Ȁ2@1 a`f͎; 2Db(NBH"(fv#)c Cq ?ovitHFэc-Ad;E@2Tڗx(=^Pq| JZ$c8 =7|6/HNB 2qMvr$ˈ j%2LLL<" k6p`͔bWk1'6qe)E]$AgHőCj'@*Pj(BЅ2}(D#*щR(F3эr(HC*ґ&=)JSRf\)Lc*әpE"rJӝ>MGӟF=*#3!Nd<*թRR]\NխrUOVNձՃZj~58k[i]zDw-+*=!E,d#+6+1)r,hC+ђ=-jSղ}-lc+Ҷa-ns۞V 9S -f4s$93I1LRb*\icK`1YV"@!I7Jmr\HιVk;e)8MWqPe7 c}+4x왤~s Wr 0H)?ˀ> ;Nc_l.q-'I|u"΋^#=]ҥ^rbRӯk=\ >u}_JsE $72;S Do tx$;m >F V`Z fMv r `J Π  Π ~#h! !J?$BɁM?ПaVa^!fnvR`F&D&l_%_&0x0ğ3YaŸ(X!4!!!_hĀ#_XBlB"<5@0`aP!)2" ."+*+Ƣ, -b Ÿ&L"(!&X/:"(T/!/10$"' E1&-#5c823-& b1ڟ%\]B~#9_3A3L!$**c#BB&dCzB>՟.j%C_$A##ZcXB=Fzy$H$$dIJR$K=vG$L0#b@M_S™MNS~`T~TV`DVe9BJΟVj0R%!GΟLA3M$JV_XcN_YeLYeU hFAe]$]ƟY>A@AARRAS>ȟCIԃLfe&R&IX&fvfggr&i^iBiffcjffeJfj&hflfhRl&hfonnof&k&q*gssqfp2u"h'wNk w&ubk^vnJsfrfea$_W~%F%O xzgwBIFʐ6֧*e`$]wx(zVlRo'oJv脺bc&0(f3Dc'{f( %cd^2UNeF>e$NʟWR b62cZ$^^UK$^RE`Eߘ^0$a" CA)Jd>6d."dB1!$(|ey)abL 啚cq1*H:j2X) e5i#R0;RJʥ3>28~*a4F@ bb&`bd)j%B&F2(Pce:߲jʟZ5v VBʌBJ&$mkӐ (_bb⾲ `j^` bæBl*2dnbl*eZBeJ_*ɢRΫǮlzzk֬(lc,fȺIGmh)U֙y\N=-}Y y!AiJ-զ-YU_SHhى̅]]m:-QSym$B<`^lmYӆ]uM!+@b@ =zh,F&>U iEu*ޮVnQȎCĮGm../׌F6.YA292vњp/-FV~/://Ư/֯/ChAIo7+KτW`ȌC8IHPxv}P(|W\xex+xwyΈ ɠx 8O  ,099  +A?8#ߨk h{ Рym;~~!& R(ҕ0#"2 /5(9N:4sL=5S;!tD!TEm4G1Ts 4#4O@P(OAQQIEH#4JT'RJC4]5[q!Yu__uqXd%6e}Mv hVZ]۔DoU%sK-4ܴ$YWyUUW@e^+N>Y{MX)Xш-X9AYI.QNYYnaYf%yD"'^yyb`nτh|CTJg8v /D=6ydXv :xS]W:+/YC⧨veP!KMSopww91-3ڕ/1fi>~Hkevevoos/wgw6kW\0ASb뀒,dg~.|G;JRBoWʖ:<_L3(hCVJ6>#sD84Y'jAX%4 QB-t aCΐWn((<~XCJ(8a\4':aLl"'nTP%V1 bn2"h䢟H1%+c\1IE'K_BCB u9ҍg"$ yIJl&Nb$[.RJ֒HRb̐eGKUelE>AfziH2J&"KFkD*"CGZ7;6At8gWA8eIsy$-85qғشOiʳ^$KFv_??)%aWMFDdbE. x*^/v7۩pϜY{vk|AR=8wIozӓܻ\.Uay*~rG=R|y'o\{飷߼5m]|/yv [9L7fdw~OlS*ɯml,M՞ o,  &ټKP$0lKOnIf/%l N  6t ׬ P B P  p r  00Sp q#!%qKƨ.MNp;OQMm]  #PwwsqQ-W/7 pQo1ur Ð1qQyQ11q-̱ pp+pA1 Q  :ky/#Vp@k!n#C T2ZR^2&pB%mRVRraj&(i 2$ O&5(R 2j#+)Yr,.(R`,]@. d!% /3'. 211s1#s2Rɮ 0U.3//3! $ n0u257A3Y1]34f6/:/G$+͎9[2ޠ6+/r!%Ȅүo*A B(׸k )LLr=>U@3> @==Փ??!t@@@b2AASBCB-J!Es%)o#ۆ/R#=F[rD@=U>' D/\BŎDLKtTTLKϴ´8ה޴.NNJNLOOkNuuPCPQݫPOɋQR URRRSUQSS5,TKSTUTUUUVTVlVq5WuuWeU WqkW,HXXu:i\DcSjZɶjd][ѕI(G^ijEɷ2\5]UJl*`xXv*_R^ Y4^ ae*actfLp vcjZcEvdWIdQ6eUveYe]ea6fevfifmCFk~u @fVͪ1"xaiiYŤ XvkC%ob@1kfl~"pB&Z -6nE 6a"GFo̶''~mcJ&d]+Ewwq9 EqOFFE"HgoO RVu^'`j|HT$uwjuu_cEX7vr͉Eo$z'7I{|Hw  Wnw](tpc}b}F~6p=Yn|E*1('H+"p҆d.J|7Vb1&9,h;;~ 0rx!8%x("'"18Ղ5=9Ex)'IQLRxYX[Q؄axWi+u["w7XrGˈ؅mxi8[xX{,L (劁؊xG8Aux8#wqPQ؏_؎ y +GS8TTx"/3Y7;?YyG9KLb#ٕYٖQ9EY38ämfY$_9⦖yyy yYwvLٚY yXW%e$]ϙYyٚuYٟ:Y :Yzٞ yyLؙ Z-IzQ5%Z!zSeZKzڣW9ڧqz9ڥox93ZwYXGjחثX㘬Y/جm:ڌzzѺ[, j:X}ر#[+۲ ʐ5[R:?[CےGG{X%K;;8M;mۈw{a;{{q[]u{KXw'x ⡏;-"65={"ܠ:!" R[Pл#"P";7v!΂DVyy˛zl'mf{6: ؀  a#@|h"(0~m{GdVy0"o$o Bɡwo1Mo!lvAk(#~{\ do bN<ҋȱn~sU$`Ȼ/ 'WÛ N<: \!ӻe)")}"$+rJ \W".;>$" xå<#B=ޢ=} j!>%~)-1>5|MehEތʍ6arGBnd<])HdJp^f%E(ula7ك~e?z_}guNLڦ65bݙ~ U]W >,`cˊX~g~? 1$_WÃSAgW o?&i&"4h0I } v[B]&]cId,%¹f)&c[-<:`vIH@hH|l lĦc\ V ĿV'B]#vkEux8F"؟ Ƽ֧HbM&W)QrIBU ^Z#a!EɊl2ȑ$K<2ʕ,[| sbDnQ`b 8ܻdR~I@Xb/ on'+lH|L9m} .`>h[_TFYG~4ZED8Qt6Sm+(dr!FTt)dJ.$YnyQG5+b^~%6yJ!#a-c  dCR#A< %~Km=QDm`> iB(F) D_R %(EMJj(=b(fDbnꢮW[1&k0k wll.l> mNKm^mnm~ nKn枋nn;n?̋ދ/Y7i!暿 on03|pOLq_qoq r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxxF…+c`xsM'ڸ7oqy杏N۰1pSz뮣 84z{7s{SN|ʂ ̈a@!D,%%'''''''''$#"""#%&&'''''''()))*O,-1111111111111111111121222212221_&ht!hs!hs#hr,hr3gr5aj5[_+RU%LP"HM%EK(DI*AD(;<'67&12%..('($!#!"#############"""         " #$(**+,.13445665566666<$A(B*B+@+@+@+@+@*A)D(G(I(K(L(K(H'F(F(F(F(F(F",G(5L)7N':Q#=Y#?^&Gi(Qr*Vy.^1g4k6g;a>Sz@Gn>Cj<7mhDžr3THGJҒT`hHJWR"0LgJӚ1u)NwӞ:PJTHM*RԦ:ըOTgԩZ5UVխzըiWJ֝hY֗pE[ֹv+Y +@A^+`bC) V*d#+RlS3Y Hmh:- /*VщMNA+Ӧt PXjZԲMiNU"rM)0Z*\A\hSઔ]%" 4T )x ^"7=Pӫ^@W+ߛ [Hi|R.< Mi$H} ]  8.:` YS* &~P}xcɭVcϡ=[YB;ȆF4-2_׼7)4G qJ%0k:|H*Rq<֥ީm1_v5fƢv)=Q6lN[ ҆6m"d潶lq+nrS6-p[fwxR|8|m|7 p;_8"pK\&+펏~xg<l>ˡmS|)o9]Nj'YqkL< ĘoX:זpigճ^tq;H)0DcFDƻc:^N)R .3S_*%<Oi2R+ h5eқWl{o;^?q*y2k}/ ^i!|3cXX0b 03mFUh+Yȅ]ׄ;7^'cȅj]l3VXfxRcN4PR颇gB0YPQE@E(IIEd}x,XzzONPOitxNO$EOatE$QO,J!MRI@QD,ݔ,h8U@ rMEh@H>XԈ֔r+%MDpL4LɴL(Lď$AKԏ IL,HFXM$wHX@ HQ,(Hr]s ZyO$At6dON-,IZ~xפBPHYQHI-KUM,CIYҕ沕^>dYshI>jnYBQT =r@  NsNQ[ pY`N1 E   Y; \oITOD0sV1}5 LD@ Y;U9PpQP[)T3wpq`i;4ϹyII5YǙPV6Y / ) At[A)ZVǩAg 0D +a6~9I9ѠdɹAY;¨Mݸ ))V`9Jg0 Q 9Y;\ B\P!"{WsPY1 U!=*=qٝO:TZVzŽ(\ڥ^`]MbZfzhBilڦpiJrZvPwz{ڧvJ~h ZZ ^J\D:Z訖ʨ*~*u "D pUJz:uZnʦs*Ě1ID4Xz}jZ_zJۺڭo yڦڬXNz *JjpJʯZgg 媰ʰ *je p /'TJJ){F,(3m.kpTv;4۲@k> TH۲EJ۴+{NC۳B 2D[#T^;`+VW{dha;8[lK_˵%;˴v[r+tks~˷w;I$ː? ip۵{Kzo[IJLSf[[ɸs<˹QZPzk;˻ ˫M۬x@ t *Jj[˽KĐڱڛk; [+˩ʭ{ۨ|Lļ  ;k\<O, #<|%*| "*1<6X:AQJ=L?<A 0Ez8{ٗ)Y!Hp٘Ap OpS,LZN,s`YIJblO|ZO@+~yD^`|r\P ŅL1UPmLpL@ j) I bD$9 [ʢTPᚰ)<VDUD<ڞ[Z)Ìĸʻ<J1 Qq޼MZ Q e\OI5J2T,) ˽5-W0R<lD0-N:l)MD jU֩[+P* M@{ QIs6IH Z D_MJVop} ,KQ_ 9,Nj!4 @jCZ^ZaNJ:A@^PunzKI-{Τgu{,DjH.Oq>镞|݉ 1";>^~#sx0Q;AQI*:>ЪJWɌ# P 9n$ԞxJL#]DøÇ[X[ 4/D~$I5:+dD>?y?]t"ԪZٔW* c CFؑQI1  DPB >dhB%N\X7zR$hő-]UTU^ŚU+S1z5Iбa6Z4вI {lLJvwܜd͊ݫ`U RpMn\Xdʕ-_ƜY3΢ f9緎 ZriK#Zta͙'1bq3,DٹG\r乡2ՕOwj];Noe}6 _ͳNo;}&O9E$,0T:t sP aazPKL R1B/l1MSqE"dƹxE{R? i,]qz/7'2K-#3M5c5RҶҪ+ņk:CS.-<6+LܳPCE4ѧ<β&S5iӨrR4tCT17bkJJ+?EgV[,Xa=jSr=4:SrUlչ8foZk6[l6n[q%\sE7]ue]w߅7^y祷^{7_}ߧn8`&b`F8afa.a'#8c7~c?cC&MF9eGVeYv9fa[jf)fNXyh`foYiZ꟟i{ꮿFl]zyloi~Fyido)g!g!fmn~FhAs!gt=kؙWgn<+\p3߼K]O`^"ƙ%1לs.=YxA [p~Я>k :O" o B0SW,28<1EW_8>v{.t\bn?p|Vd!,Q򔧺N"VP4E\Z=nqf 9Sr<:kv Az˅\7ac4n4S0NЄƴߛ]iw,-y3&-JÅzԥ1aK^tqȁ` 0Y's-]oQl!Xv}g5=cl#l6bmpKwUm0m,3]*vm$V%hd"܆1 3]0ԷDd8c8*W46#4-a[B- tЅ> |}go?u'O#ʧ͑~/ k)~7}~s]==K@ +?6y -ZI/Z9I@RLYAiALA\9AA B! # %DB&Ԙ$tB#B"Š*3, \+B/B0B1tB2dB3TB4|C9tA@4B4C4D"@EdFtGHIlDEKLDLMOEJP$R$Q4TTEQ\VtGDWVYR[EQl I]K`$FHbDEBECUT@eRo,HFxLTF5TKT( T[Tl^ B|ITY<ՒTMSV]FESmUX}4U]U,UDDUMՓU_S+s1PE|g_ `}BIF0G$OLOqp5MNrVuLMsuwx5tyEWpWslWzuW}z-e׀WVg4X|UؑX+XX؁MχXEX4h*VJ]TWT\Y^YfPmTUcYd]V=VMVwك}`_ N`W>O_`Ve؏mN fsa"a!.b b#aY(VbYpPEF.b0^YUmuc}cccG|%cc-_.Z-0Nc4݇F.EGILHvIʵd?e@d:FO]JQYZ[\]^ddX`f_6P_|Іcff> fP>H'Xi~dhUV@o,H=gfkfmfn.qwgg;@I&%pdvfVpChvhz‚dX}F;X'(fHqNTp(`y)`g,h>hfiv d=hf%``kh6VV@i."HF&d&v&dfW؄؄WXF%f^k^6k&&꜠8.pjCfƦ;hjdXaMj&ʾl"l"llvv;hkvm"+=c&d(D6D6D~%ȁ.DFDVHNnl,xnMp(D6nEfEC܃d8oHFAV= ֨G ni`큘~Vi o W'셈C)p jlvi^e8m^x!,Xwk_߄k w qȂvex"7(pPp0B\f")g6 r,ohdpflkNUg1g=<=o?G?7&pgjADotF'ߜWHh7Qb`&TWUgVwWXYZ[\]^_`a R"|`bgfwv6D|ijnkm]6v}Gq'Ptvm5rgjذwOf7m{|'4^nHs}xDQh݁GWgw^7ym/D`A/st~gMNB_oi愀ufWYan^閆&NU\h˦hpi"i"`'XGnzpzΆ ujG>{ {铖u`Іw pW'mӖ>6kf{.*ɖl|o(q quY_]p WϑH X RD4V .`iHz!!R%^߄8j+x(##H+#5ކ6 9$ HjwX#D$eJX?h%a%Nr%Zff,&U99&Iw^7|9'b:Z:z(zh- Fڤ&i^:R*hLHj~A:+AILJhfz3[kF6,-8p ,i8q^/:Kc¾Yl} .6)iຈIʫۦy//s0 ;0K<1[|1k1{1!<2a 6%;4;15 6H 6.s6pč5)9x9vٵj:U5%SMG' @H+M1eHdh@4l,@t|5sMdc4G@m@|8k3=Y5W8s qs ! > e݌9lxEv=\6S^]9~%9yHD8ľT:GG9믹ݐ~Y6{~ n=o/kR'?RD0_?g?O%w?,6߃|6Q| w Cۆe󆇠G{A]5`(\q 9a n0b\w( kpc@3ꂸBz(nkY$-~s/o?,|eόګTbROx. !E.QDς7!s^Ө6ru]"9υ$LM{w>۸t_EOL'>!|ٳNĀ9W=- ,e-/jy-ssY6\H3E43jӞ4C-QԬPUծ~YӺֶЭs]kVּ5_`G26je3n6khSRmTg{ޮзkqۧ>}n;nw `>F!y[6M /8p]+|nådQ1> {8!rW|.ɱr(o`3CoVe=ρE5ӜGfNkCz)xud]Azͷuz:7<=]PP:!x;nw;J}HGx"+S>`_.G>='5?zΫ>O}R_xKOo{Կ|>?>V7Qoz?WO)߼a{~qՇ>_}3MܽKeڵ\2ٕ]J\"\Xq>`JzN\ F :\ . b`r[J؛E `٠ a Uܸ`):AaJaQ!Yabiy!aaΉ!["aa!Faa^az[ n j[!V!R["."$F9quPtXt`bth"tp$\hEmcZ rm8Cc4HM8,2YHP@#;N+"8nl)#ÌqP4Q@>xHYuF2l$jpdCx$Gd|)XlW2JBDK dC$I2d$OO$lP%N6zWH$%5?M֙JBzWOTPe CE3eE, xc%U@BVFUu\dC$#Hk[&l6]~u@()2<oXG 84Bk5'GtfJ0EuDDA.=pvxRuFygChg}etx'9FZh燥'&dJ'hy%ewX~Fh"hg#quELj( فx FzGh( CzzYǏH)퇆Bƌ&f؍€X\钦_Ψiz)~){cY<[~ 8–Fvuv(QdhDȅHp8SVDv@XnrD!djDj~j$jm^*EmX@gf6%HjhWK(k^jEntEE6*EQQ$3 &YŞA8%*BDtZYk]kjRH@j`vBhk f+jSkq@dAZGJDcNl8Lgl0;3x)41⫽g/Ck~_~8}sG[ӽ)ֻ{^.l )C/888}˸8 xG?ۃ}c??|$|SWO ?@8*L8aC!F((_.QڲɚJBPMTeĊ:vL%H$ B(΅-u)hQGV)͆=wd iUDfպH7v ܵMKP6:X2_ K8F&fRts9sR,ZijA ~0⃋U5~y䗥B}xm ;?M Z4Ҧ3cfp3 Mފl!\ٟ-nrډ̗>׹8t{?{b\i΁f851qYH5GY펗1d$ XC2ɀ*QVde yw׻-`O^"`KV 'd+/R b#1W"c-vke'/V! &^aؼ)&)qBmhl[F9OraWm]HC &cJ!Aqb621׸{.X@{64 k.y 0/If,t7{MCԩ*6q`xPrakYgE8noZk'2V b"נwFxY/ ^ƅ^,a 039M81+T1bnXD E>|(vt>G xuV:-歬1D8n˽n p/ w!qO=|eo8??r%QNr-]s|59m\=8}t&@ѱtoKw:Ru'K4:՛~ug]:z[>|ޡ ]2g{]v\sGy{y^tBӛX^LosG>㔟-?̻};P}a/=Oԫ~zo1g^cG>~"_~/Ok-e6 C/zYUYj_uzA;٧>}?矿/?oݯ/O /ϛ$'P4p:"p߯)S\PBOPQg00=WPoip[_p! upW  dׯ+ 3 ,ސ0P0 Mذ KQ#Q` 5ONū*OkuO?]y%fQ쎱QꚱQD+qQqqN&Q֑i1q'j1 ݭ~ rkK !`+ "~yz޴!0`A aْb׸"6=< +B'?')r" + `(<+ -+vľm^^2&2ft'l|~ֲ-$ &rrr2ȶ+r,Dz0a *& "FM-ی+&ò AA S2ͰbA|JM0a,+e#`` ``Լ 8қ `"f V,.Qt >iF26ͳf~/ٓ-R+)+ 2}Kd?=2+"s< tғrۀ׸۸ r(* +r++!AO, _@eW=2"43 Bt+s#$bG < dCgJJ4KtKKK4LtLɴLL4MQ܈ !tN$+NW+͋0ٔN*JJ˲3Q%NJOcjf-Q5էTbROsSQ5*jUO+*RVP1QiJ5?muXߍU R/+ ώX" 4Z5[wZ[[5\u\ɵ\\5]0ٵ]1+AH19+$EI$ȡEuְ@~! "_!k!"5+7U#92I5$_`O%A%]t"+AՌ,,+r*SxĈ`a`cM n &seUFf+lX@WZr+(YA+0 z,Ƹ"j2gLgYVx rhɖ+VRw1+A62u+*5 D4>V+p-6qfI4#e%4r'4Ss*Im663 6s;ͩ; &6QTuz4^vvLMwis7s6a:iv}x cXTtf` o7+jڒmICc?}ԁ[`9"aw#<7yHIņTGwT|tޕx8xɸ8Z3FXTԨZ 5⸲Z O'+RIL xY8YE5T/[ukPeqkِ QkWR)K}TyRY{ -1P969i˔3Y8 N]u`ꗃv٭9tX]#Y79?8GgYUˑɜEyٝr9Y]XնZ9:z ::k"Z'z*z(.i2t2zSGGŤ[舊@I%XZ@z,ywRZnrڏvzڕLz\e[e@7v'VB~{RĨڀphrj:m!z'5©VPa̡&rdz O f~Zz;"BL{J!p;GuLDDnHjN@!DLu A !Yc>Oޢusa/ㅈ:i:2٢9Yu՛>^UU󘏱9Lw՚#k~~tWmyYY)VؕU+kyޱu _YyԞ^%%5^WKb Wm9-VEYi Mva-o?,!s_?WWCLQby?!? vSߞ%ߵ؎^ <0… :|1ĉ+Z#cF58Ƌ$KVh2ʕ,[| 3̙4k<9Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} 7ܹtڽ7޽|dOໜ~ >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;ݼ{ <ċ?<̛;=ԫ[=ܻ{>˛?>ۻ?ۿ?`H`` .`>aic#ab.f#Ά">M6!bVb6΅0rc>Ҧ (zcFM*N>? @*X!,222222222222222222222222222222222222222222222222222222222222222222387`9{{9p7m7m8o9r;v~ACEGKMNOQRXvđƳﳉuWۿLECA@@@@????@@CBBCDDC==CMG@~={?y?q{Bkt7is,hr%cm(Zd,QZ)IP#FM%GL,IL4KM=JNDOPMPQQTTT`ZYt^b]j]mdpirfsw]obNdVI_MHbHEdDBd==c<=c<Iɓ(S\ɲ˗0cʜI͗5B̙Λ@ JѣH*]4Ο dӫXjʵׯ`JjwVٰpʝKݻS BeV[P ֋/#Z`mn, |lϪfuڶꟉ l}Ayig7Uӫ_Ͼ=M{>mrk y5~ǝjM[t5_[Cc)6Vޅfsgt;HYxmI`6'uԢ!#DiH~[}Wr.Rfrd]w,bIB:qMޘl2$mtvۊ&萅9t֜ bс磐F* 2%dFJ皧/jZfh嗇Ӟ}鉗ڪj&;WVzDL?>tf}ڑ܆+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(Lax gH8̡i@ {(";$"%*PbH*V".2^ cC0>4#xC52t# 9v3a# Bi"q 2P8" n<@‘K~0"Oz0pD"QJSv@']qC}JElC 0Ib󘞃20Ќ4IjZt&6nz8INlL':ϩvӜә}Ӝf?ImAg.eD'j?тriC!J[#PD3Eя.}9ԠF!veH*3vD#E8`r&d#[REWa^lihE;ZwZ<-j˚WB@mOyQBـe6LRUmp \ʒG"ơ\[ w,.j[Zֺu/ Tͫ`[Ƃ3e+ٺt{Y"Hf{\E "AF,".<x&ȁ#<+"$ o 0#a#.1S~8!1_Lx"a:n1cF^13YHvedPβ<)oRrW@%]"I.^[rmG6K^fs 57%{-_+XÏre1\NiM.t?-B҃;dQz3~5ɌiJO:6hXa%n=3mK[d63c&׷MVnW!M K.־E\ Wvj&! c Ĝ)efnyXoDx6Fr8i*-pV\lqz΁ow16w^/JpdoǥsvU3fͳs\?w(5/:^neɌixFS2H;99i9)ɌiJ `I:՜y  YY" }{Ur-蔱rWhBf ןП)$wBghȠ78IB,ZuFȑ@Wgx Yq|2Ju0i3 ?9< >B:DZ4W dI죤 T>XXO:K1^>]Jc>GJxylڦnpr:tJ;W{w y7')Œ:wN繧ꖄ:BXqڐ^IP|{&~AzRʕ↩*NZyzkzxꖐJڐM*>WڪĪĊ\zI:ꕲE᭚ڬXΪKj: l*$䚮jv^κ:J(ZLK 7j i&"ivB i,k I%+k $TEx5[$#kvZʬ*>{˰M{*#Yv ѰY+H!`N;=*Êd+귷vw~Mt˴˵ºJ1yJ|[ZQjp˫y[JY[t'Auzۻ,x~J=k[JЇ{=+y!2؛_׼ߋh[ _绾lKл<ǻ  _,֟Լl ֩Ep[M`xe~ M̂=Vm؈ZM֦ <ٕ] -RىBMԟ`ad ÜJl,-]l`mٽ-ؕ3N>?^CN=V.=:=ǰ/P*L.m囝m`JN漌1@  < \rK^lxzn|^័ ,r.L w炽 Nln_^ u=^t\<Г)~ ~>An .&.Nw]ÜAYݩk]no:..p&Ԏ 99 <.~m+., սP^^^nqL * oCY7m^_n//ab_0/[OZN6^ ~Eo.|lOj{_~ erT)[XG.m:ԁX맯_Eń4K6'A7N#zۧqέn޽LSpGdj[XmnNOzj\S捝?vXb;S]u剗ƎMQ͝o ZqnTbXJ`#Vzt'-|yz#6'VU_ j2̽O|Bp-"(- F5!l6ij 0"j\ DiB 1 #h \"I/# H,MrFtTGLDR#KOdܐJq ,yK#\2"2)4M5Ѝ7ٸ2E: N/O$?lLLלr 1sTGJӒJ׫4ֱӃ5P's ITT b1Dp9Y։uZk̈kV"m}ۈ7VqEvߝ^n^{UV^t~>x̂%V8=.^b-cIX0;E^c?2"Uvef/e-frxFA:i`i:jjnj뭳:lǾlFlf݆;nזn;oֻﲍj&\ G|g#g|rr3׼o;webod ә[urkgv޽q{w^z,DiKVGZvߜz^~{%/[$QyǏwۆߟz˧->|1z'Q0l+^DȄP#$ C8*,T! [5L Wh7a:a4"AbEj4TbC*nш]|8F-\,#C/ЉjcC+fql xE9z" h>b(62$ ߸GT+I:Frܣ&CQdDF*q%WbptDAy0'sw2kpDtA ּfm^&)q*|vfV,)yZg }Ӑ Tq=(BO1 ](D&QRt>)(EQq]HC RN#UE{Q4r.L}wRV7JyR4q>NI*Ԗ: O5 1z"uԩ:QEӭB4jJ-z%-EȂѢB5G}AʽMC҆P zDcҜksu;lܺX^N` [ľo,"UK5>Yl&ֶQ5ZVc&hnyR!r{֑'{Iw'Ľ ]皒sW'=vXnt;喷5oЫT7y U)µosV0k;ZVoza^|&8+qw%an8,~\-[M!T=VshBX+=";"FƲ^5Hڙ&fi˙ +fYj+e rZ> 4/ٳq7圶EVMt7 =>ц6'MMsSЁN]E=jRԧFuUjVկue=kZַuuk^׿v=lbFvlf7φ1 avmnww=]O"սnvw=oc# wo~A,H"Gxp7pWx5m0ayE>r'{u`r/y̽G2yus?zЅ>tGGzҕt7Ozԥ>uWWzֵuw_: `:Ңo 7P6t쾆:$h0*vٺ|}}G^CC ~Py[A R`p;y?ev]A.p{ :Q5t 3E'>su6A~N%I h/ǿyoGoC=9dЉd8Hg{? ,@8\P@@Q[? D@@p37@s:=? ? Lt6zx)B"A]Hk, fu㿻69o[vTC#B=@4$L ?9@AXxe#:0BADۛCÝCzCzX=lߢ"v?7YnPnDLGd@;8\ZT::c>/ ?! {h@ ? Z@Z@R=FocSht@8_\)b_km̽noZtG=C8:EoG`VCPpKKCidHC4хKLܫAh܉ ?#Ch{ QH :A60? ܉UPIE9kȟJ^34ʣDʤTʥdʦtʧʨʩʪʫʬʭʮD0˂؂hʲ4˳\*SD˶t˷\6˺K`(`˾˿5T$53T48hZDŽLhȴ$4DT͟LA8<֤JKw̐? =ԉ jtM\͵dK?DZB4|L\K;|<+IhΝ6G rk>C?HpX2LN,CdεTй؃Nr ]Pg=E=c틀TpǏlPD'<EQ 7K Gl@_ v?]:fnD=fQ`E"f>mX7ed>ޭdݽfHeVF+AFE6x݃<im]maiEviPfdgfovnfgW`nHcHp8gqr,r(=r+o7xv]"^>h=F^1w2gq7+'fhdQuCna'Fp8"^n `!xek9fFfGfHf&48d݅>`2]6f(dP7s^$N0^O/cQbWsE[% NP't!.nctY>ANv@^>nv4/^>j9;X-vj9Y Ywc/ws?Mwu_w_towmw{Dkyvv}|wS'x'g݈xuL̊" K`X֍,̑GyhI˔yv! ܘRmWMjZWZ3"YWgwwSHzl;ΦH,`:g O׉wf3{#({ wHh i ¿8HH hxhG{oSwy"HP{s{G}ׯ|f{}dϧޟgC_; ߉H~߉I8\e[|ߗ6I@ַO}~}|~{{ߗg;XB ;m",$^l0B'Rh"ƌ AX#E;N$ʔ%m!H ri&N*w'РB-j(ҤJ2%hd͂ J%IjSc+XPZ-58ӶS0@*QW$ĊQgH4 ʒ"1( ȒG<š7s3ТGG\2fԧcZ2atk3Wܷ ;si!EJ GwjgF'>#+v5p߿oG)Iq"Yg)R=ӯԛc }FGDXprV7BM8D!#GVX2)"-XIF~(X7n[1AqIQM‚BCL'u(H'I$Z'ɋey&i `YצoX^uzTٸap4'F ޱL5E,PC-ʐ}]ST¹&j)r6&@z***:+z+++ ;,{,*,:,Ѻjzj-zk$A%}{.骻..;/+Nžlﶦ׻0K=qK  bqXs[!YS)2"<35-YDC73A =4E}4I+4M;4QK=5U[}5Yk5Ha~c djD\CfBH"3 7c!E *%#â1PS[An8)θ 9}ܬAy'x=߁.~;E>_c\|¢c橯D$~}noqCl_7zXއ!}qu|ك R`3I+C,OmAt?ԏ pPA"vN":@wd  r n $C"]A0 7Bnh"/@ 'BІ8` #`qЃ K(" ߀ D>QCS$VĢ]D>羷vcD!C&(AJFBr +#H 'rd*"$ RI\24&;!K%)rʂғ\+w7J-="P2у!H` ^O.aIʂ S'F:|y|d7(Nǚ)2L(L7wIs{!wMIx/}<{D:)C BPL P@ RS(}ː4&%F NУuARzH0)OcJSni8 xǗ'mT~k9\у8rj>rç$ĒocDĈi>fEk[MΧz2qRoڕgMFZj>l5`m%dxU+[rcs,v9?*`ՅG:b5z2-o{[ַnq\ʇVPVqfm) IRˢE 'Δ ԀR}H8Yo' 7}+_aHH 0p|K(M^Qm=y`c&iA:UJURXN80Ej&zH!S 8pp 7йYn?}" w>򝈏,dL1F d&~L W2A&p2&\БVGN'Q*O,"y)lR)⥞X>$/xv2[ YEns .Jo^(}+~*W,JJ,YGpo[uk0<{q}`Wִ6k5qOGaT߶G¶|d1.}Cr{K7>T9s,j#Kzd:ƶl8YqXUS򕳼.9c.Ӽ69s캅|.}>hL(3N?M6=V:O㊭^8Md?;_vcPG:=~+ַtݙ+7|OܺOM_n /Eb<#_ua钿UGUQ~/i79[p&FMo}z}~p^<IRN>}gxOQM գIV-fAZ= QH) VR`y` a`‘`:PeQEP•ٛޤأ $D_ J_ح j :a_ZMmn1!Q 1MPS26EdnNaila:{5a_ aa!2b#a||L "=9]U= `\zD$! p-].{"]"0Ƈ/." #|."2֢33&-c2c&rnN~hTcA7%b?2d8d aD"a"@IЃX^׉KBBCb%E@֤E$h`A`Ir JN&[٤OFFƙMGZaWF| dJ_EM nAD:gZSe\O[6d`z_z_SazFsP[AҼDJ ^%eӈff&쵦k&lƦl&m֦m&nn&ofVЃ&q$5q.'sfJNg6tNg_D'h vzhwj"uJFt6twz{ |g͕P'~'\!j'R'A z(&.(6>(FN4^(^hd(v~((z膖(h((h֨(ި(( )).錢Ĉ*) VBJ)Z)b(Zv6×6C)院i锲)vi)$d# 쩅h|X-B@ @ dh,@ 6C"$…jJ*^+@ĂvE6*NjZ誶rނM*F*Z(.VA"**kjBkJ+ +Ҫ*6 TO +*k+,jNk^2Bj.+J*⩞bh%j%TB-Xh"lZ䁅^!Y(](2 @n j"Ar*셺Ъ,l3l3,rǂ Ъ-*r6-ZhBr(ZmbrȆ,N-V-**m,-fhm3A~,vh-)hڮ-퇖ɦ-i^h$\^B$ll+k,+텮*\̞h^h6z6f).bbF/ Xrhnr/z/rhV+6 thҁ oƯ+o/j N涩ήo-ѾZhl"0oZh~p^h"Òњ6+ 7Cc( s װ0.30..,q q&0j $qnhz(cK0p7whpp[(p/p3qpЦ.^p3dp3\{ǰAڂ'{-`h('2Ar'2(r*2$1p{,K/*G*2)/(/:.$G-2s(31#R36 12n^h-k-.۰K3/ks5s5s63:sh88q930>r;[s /30~%Gs:74;Gt?sVAADzXA$tc2?;+;0SsrrpJrKsK4o}r0O0״H64$sVh4ނ%Oqn4MIKtNZhl#n,nj-1C+j .cWwB1bY[(Z{Zo[\7C]w][O uWvubc(2 .R/vrvnhahdSXDT7#`0UW4tmBJ._,rlkoZn7 ^1Vg(pmwBnnr/ws5:lK7P7n7v+w307ǂn3Dp3oy;yk7+Ɇqq75w5j^vh[({s{whm#8j3.Jlz#;+{3;;CK;S{[:b{;{sR;ǻ<-Lv;料w;dqL˦ÑVځ]Dc _f.4|ѮL2{c7_##◽?~ӻv8K>G}=>݇>g~CLNlу<|=_ 觾=J; A%C4 7??GO[?W?'&= @ 7A_UA*V:jog 7= ??~o=@D7`A&TaC >8bE#^ԸD 3:ҥd LTq.̘gO:4$\gS >*5T=^1V~^I%ѦU;fU$GA?,W|I/\pq^ۉa %8QHaGLzP|ʗ3 3) ͜:=T{jح-ٲmӥIytܨoז[l;8ᾃ#]n̑WNrԯ7&}?{9/: n/Oh-BK(ϸlcow<(7VUd5}M5+.d Fh1 ^:n22iAQ)if< "DD\_~D1+;P E>vÉe3_g=`ʪZ^3^Jl;=3?39: >4>T:5/7gS6t<%2 tB t;T@>@O0k.(B/7SATA;@a&IKr$RE[/] )cCn,F>h (Ь~((m(ʢ˫艢Ⱥ;{!;%{)-1;5{9=A;E{IMQ;U{Y]a;e{imq;u{y[\l æ \l[knL j` ;j;!ћi؛;i㻾[p!,'''((() +*(('&#"  $ ($,+".0$.1(-0&*,(%%+,,++*-/355555556 9 =AD"H%K'M'M'L(L)L,J/H/G0F0G0G.G+F*F(E'E'E'E'E!(E#)E!)E (F)G$/K*4M,7N)7R":Z=_?a$Cd'Gf(Kk'Qq(Su*Uy+W|+Z/a2i5n8r:o;j@bCU{ELrEEkCBh<=e<cA?bCA`FB_IBZLCTJCIIE:DI0FJ)HL(LP,OR@UTMZVW]YW]_O_eDbk:fp1gq*gr#cz"M(:.2122222222222222222222:????@@@@@@@@ACDHƬJǦKJGGFBB?<:88;>{AxD~IRxWsZp\haai]m]n\o\p_rhwn|sz²׷ǿ~[SRRQQPOMKHC@AC1 H*\ȰÇ#JHŋ3jȱǏ Λɓ(S\ɲ˗0cʜI͛8s̹$ϟ@ JѣH*OKEFJիXj"NS`AmHrm׷pʝKݐnE6my5;00Ħ~&ǐ#KL2ζblpfMljś獦Ht͍A t۸sڝ\ׅ~mr 6Z5鳄eܻW+]l`&5/لUK=_>ha7J;:A'[vmx& yrt]m!lʇbu߅mƅX`=4h㍗g~ck**4vp \^RHbU]s\vdg=ƘY-8jQ JYq`XBj^6hEas*XWXBnxpdi"cb詨*nex\}s6j'^!׭&Tjٰ$u}h+[i̖kvei%Nژfꭓ |(kiH*/ Q ha G,K̚i=9< ,2^CdF,y\Jl8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6op㈉9x̣>u IB2L"GD2|##IJ6Ғ̤'Nb %$A)R6!}$)W)HU{t%,giGY%.g]\%0YaӒnLI?(3$tfnjj-F@ A6  tY -XA' bNt3<b.tCɧ"I~8Mt(B: EY͉|h9xbD0(?LHHD'ZтhT ASԀ 9?x '&6O rZ`4)HqTBUfWVL\KV\9XxU `E,] b@4BU&\Jb"BZסZ A +X*u"Fe|e Ђ|XjBfR\6DM@jF,%k ]L4W5v̺sO} Z"ֺ)y[@^L*#]@ׯAkQ^\!3=o38kT9; _  =,P)|lp+NRv% >"b`"L'AvHb#M-3Ͻwی)@0#wd54/ ,?#?d_wȇ_W՟ݺ>~#d>߯0Rw{jwJEHw h} ՗P؀0ȁ;GWh}g|47UF%(gߧ)}k6@ |;(`쇃C7H2|LPR8TXVhCG5G|1xePRPS.%V1w3Wl^6}bV"fXZw> ox6ޕplUeFZyv9#Cav+6R^atddLd?Ç~xvhVqg:kk3[؈56wfVYj{`}59qr?gdO'sDgtH`ǘ5xqvu[x{Gu2u"x蘎긎@NxXHH5  ~|J}Wbd(y~JHHx(9Q IW1Hp[~}'+hkN&KHh/3)2~/Qɒ8;9>7+9pT#*80YFI07P'YIhC]ٕTG %yAiiz|ٗ)o}U)^ylFa59`)f9!q61)7qّRGДꔁف唚i֘yf7\qؚ؛ Ś)Y9ǩɜٜA XəǘE]iI幐GX(Idg闞65,ُQ9E'Y )ٟٞBY : 9DQɕF OI'yG@ z.IUI7*9) y@ʄGZIJKڎNPTZVzpZZ \`b:dZfzahlڦnʦjr:tqZxxzzڧ~j:|:zjWVj *[کYzp@p`ʪJ:ڪ Ȱ ڥp@ z x Zzp`JpiЫiڬϚ:z @ Z0_ Я Y: xj ̚J*Yj\ʯ p ZxP ̊ \ x[:˱ۯ "K`[ZJ` )Jp`2[j:䪳*˳> [ G;V[w `jتF_ZNۯ  Y* êY  @@ YJ Eg@gpZP\pt+*{_:kIb 6[꩘ ZJZpPa ; @[˼˥+; Ļ˽˥K 黾۾ʺ󛥝[_  @ RkT+ݫ\p[ ;ܧʐʀ6[p >&̥ӫp<<> ī:E" Ik/!kT\VʋJO ٫[Z#[m k›B[*`zkeo ŋDŽ\ł̥G컕n ;ƙ릓i[쫥ɈJXˀ:\˒> \ h\ ̺=ɕ[|Ŭ`;j YP,ͦL,|ikYz,<_朥\_ p@ɾLΓkL<|^:}ͬ -A}p l ALy\  [w'.M\#^Yz]+^.0jJDL!~ava⋍ACND^mL^?Q./Un5~WY(-]=nJ\\ m޾]?Mp n 8>@ζ+gdϼ[]? ϯвgؓż=p̱z@XN9~Ψ w`~Ļ|ƭJ~I{*a|m ̮ .+=1n;!, 7 |.ÁNYj Ȫ 묞 ͬ$#P-P{̚p@#%k@8pΤЮx//&;6!;PAD? [H bd; =jm r3 ~Z z 0PH%p-eJ=kjg*LRNo4_|s*nԟOďƿ/Ooˏ`jzߺ_??G@ DPB brQD BQƇ<~H%Ib2RJ-]s%J5mifN=}SPAE 4R-6iTCV 4V0f[kX.ǖElZֶE.Ysֵoޤ-`wo⦋K} q䣓)[rf9k=%3Nիu ;kk^kW}۩y - 2Өno创#e~Ffb"8.m8j7F8D M"a!z[-|W~xf99ߞy6;fv$yݥqk:ݧVf!fhS>fqԤ`ܱ[ܹ!K[;; a&!%qgq=Eq'j}CtIQB=η%]#U}GWiuO1ؙLw[=}ٗw&qgፌz'7=zo>|\9?F ;|pWY 826,o@meEs@֗ }9cDBZ0&t>vAuPW| BR}p'Ġ FCW0>:d QU>$ 1D Z\ DaKCR"kT8+Ј1jmDC,e̯=yc8.*9cΈ5#9axAj;cXC'*KS+I RrV<) ($ )MjdYĽaM?%aa'N}.?w ]O.I(rw;KfӗÜ堌'aIIͤ0y ]FR磦-Lgs<91&d.}:NnTeVLkX:VbOujT*VMnUl_`;XְElbX6ֱld%;YVֲlf5YvֳmhE;ZҖִEei\mke;[֑]#2l[ַmM`Ĺ>& q\SS]>P~úlAP šbArKAW1ov^. 8@y C~K0#~Fp`#\ b9,\!W}@AyA ЇO7|)6pc%pŅ/]GpKE&c*WWƲb-$c#/&|LKa3ڦ>X1s>fF1gL9 &`>>NFpgF7яVhqI \[14mLQcZAĦ\OӧՐ^/wWbծ>s?My΂V0`=lb؜%kGc$rN"ܒ)3d0 &vS g/N6=tؼrJ8S$%Źkc$5 =EGxWmPav1A`s g/>,Z}`S~h5 ~rF%bLwWx`s> Ћf7/m3 x- NcY8 sz>wnCsk'k۶vOwS|}@c=wQ!K;qp<}&oyܺc=hѱzַ}e`ǽh]~?|G~|7?}Wr}w_p_~G?f{D  ?_?3[2Td@#@t?h!'h # Aٱ̐Y,:8BsA h D$T%t' l(*B#+-.0143DCĺ4t7̲6ÑqKC<g'XKC5D>:fɞʋ9JK:+1ŪAMGq|:Jƫϳ@ˮV;; c<+ĸJB9DŽrȤʴ$MKc<-P 6tMJ͝M6͊M9͠X@l$ZMd'Ms6nΊL`NT@NN.HT4r$O1jϧMLT FdPNO? O e/ ]O4xPu՝;LPQDQ O@ uN|PMyQ#MtRTRRڔ O-b"Lu-|OtO+]Q/SRG9<=>?@?$ B5BńCUEeFuGHTFMIKLJNOTNQ%QRET UUeVuSuXeUXZ%UZ\ ՕT\_T_a}Ta%cUTcEeEeecUVX0Yx_ujkV]mՕab(T)@fhd@TonUXevmwWUyEJX X@ T*V]8T*0X`0.3(}Hf%TT`uTXEEX|pXB؍z}XS%T)ĉ[0F] P)XԎ-*Їy0hAAB-aٞؐ%V%ZO5ڣԤUZLeڦ VBE*@E@]ڑ}]VE/ T~B5WN* TuT)ڰ=Ա%Բ[5ۇ}ZJڮUT]B[=[0@+]W^tEbbסZ-V]ҕ5EM@xEŹة[E- T-EEېu[*uTٵ[C]$(^$]M]W]ť]U5T3\l)V+>᪕+bb)b (v3b160Fc*6V1b564c$-b6f7>c2c.c,>_%X%TZ$x])` V^ V 9c?B%.1d dBc7CW5AMeC]eZ.DVE&^Xd6TMہcXvYfM,\d0\e_nV_oF_pn^qN5=e=T6}෵Tv.Tw6^B}`m]Le]Egx`oYd`Y\Tr6]&]]J}WBWxBY^xBI-i=ix䜕hE%=@=ԎF| ^umfܠNs܈饝ꉮjNhH&THhHFe.MjTRFIF08kCy`۶j=HغVfVԭZnZVZ>ZVG j^U.V^mlTҎZ\Nmm]~U؎m>a~mNVN_>T^i6TXnNWf&nWkjvnVjnn*D LM}1 fok3M7A=pq)hۄ ||`P Q  W8> >c/O5pR  d04P2RKd#ͩUQ=AqqѶDq WP$(: 9d؃!wW.2$}R'=xgp+oS O0r/s _o 8=?@A'B7CGDWE£irF?;Z8S{DGlH=F\/'uAOa1Y{D5UF~HTA=2YуghwiE $#I0IuCIL66"|24a/5Ic1liQg0XMʩJJgqxx)W; hKLK;PGwz+"+E+"0+:z+yw 4C rzRg%Ǫ$KAt+?"y.!{.zzSb$41ƩWo?p4'cꝋ(r}")~ڨ}O{&Ϸ(/}G}}}?ڇרw/o|_}r 'O}hooZzb}w~Χ~ڿ~/W߇x )2ʟ{7{O,,h „ 2lÈ'R"ƌ!"#0GNyX$ʔ'Ml@0gҔ(&Μo쉓Ϡ/ -Ѥ62u)r)gTHr=+X_vKٳJӪ5ʶзp}ʝ *ԆuKvߙ%\0S*^1]BNڸRq5oLg:/NHV)y5װ\¹[ʞ۳w&wk䨕f.9h n:ڷs;Ǔ/o<׳o=ӯo>?~_ȟ ~ 2 Jh)sB)x@ (UF;3<РExc-X)T/8c7jh)я&,E2h#L: %Rb"QXbe8b㎠IP2'Wb4)70 癋-,_4VPo8YJ&:ǟ:9cHygzFgnz9ɖ~=vY_w C;Făy:?_е&|8 C?}M "#7BH}H>o~WiȁUw# 5HN&'H}RYuD3g:C8N1ӕC.8/_-2`L5"10n/s]߹~sz fϗυ4E3с6#-ISҖ43MsӞ4C-QԦ>-+jXF~5e JD"96b5;-a[S &iu`2z(iS[_*fPL^s3]Z4L478hE> v`Vm4>XF?; .H8p3fx'qc|ob%rS.xEro䒦7(܍ AaF0C]X3}TAQkkR4 4^oTF1i!׿.K9M瀦Vnt;_U ?<3<#/S^%Cyn+!g4VQjB$/o=T_~~kWoo g{2~C/|>/Lz)>2s]$MX|L&/ÿ{ &AT>UY`]YAIہi**`Q2m`_F`_1 X zTV !̵__&a^2:AHZa `i  `X G` !aFeԡba ! Nb#^#LbE"&X!^&%!$k)b`1)^+ b*b,r"ab!jֱ_j%_-2Jc ޢ6N 5M2R]#~d};i#0v`:!%TFTN%UVS2UfVn%Wj%Vv%XXZWYY%Z%[e[%\FZ%]\%^]%_%vP^&%,,aBe`"`a6fR&Q.cN&@&H&e6ebffffgec&iivfvRQb%I,% . )B,HSRV>Cj"&Boz)Rަ!m(u6pq >TAr:l[m"tf'X'v>vv'|疈x*%SRA*(ezF'{fR'Rgg}.QJ,xH,܂!&nuhR whe&h,4I,B @t)v'RC|jhyҦT⧉("jhlhfb2BnogƧCj& BF&e 0fƒ.Q"t|nCF!g ”.,`)RƒR6ez')J)Zif)ܩ*R)i|n(,(R^)*i>jNmBZi&28b,d)n.0)}AW›ViTJ**Rj"Rni~i&.)R"Ce C^&Bv*6?h"?|Q)R+̨R+vkk&*% ˆ2hS&lR.l62%6f+k S~j* lRj,J%"+lÎ>g)Wn+vQz+*ø&N'&Qb,?iɺh"RjR,pm|^m|Q:mRmS-,>m&(,H-R­RʭjAݪmFlSؾB^,^-2%**έw퀲*׶|if:V%.NSZ&"A0-k뭊mt—.Ê&)lCFl$m*/F0NoJEP/bמF*6Vo^&%rZoj.n*nZ/o)c/oRW0ohWnNj0_0f og~02*  G 0bmM kn+  opG/3_qg1w/)~Ɖ)/owu -$⮸lܢRznkknBq 7ep璫R"2"'% -R>2"2%[Sf2n2$ɦS srޖ.Rrr#iFfe"%+i/%V2#k'0$l+JoT k~p%K3Z3uZf*% Jr3j:Q:#A;mm߾N<+6ks>s?n3A=3;婂hDtRm|^4 oH3BJot0 Q64(_N%driomfnsnto۶TvpWoW7o#q37[:sftevHtucwv%to7yzwXv7x?xwx7bֶTwyx7x˷wvwvutwoWpwVs8s8r#xq+xp3 Cz%W_8YNFTЏ9dE!H ndI8[d#/bS8FNd=8 N9e[)>V#9U'\(2("bIW9Oډ_y;ң#aD9+B>x:dH:'/:7?:GO:W[qZ_ TzluB-LȭmT<83 3zpB+9EKȨEL:V*i#iL}ۚ%h)p;G{%b6<۴i;۞ŝj]!J}%v9;&b󻣑" J-D !P݋[cB0 7˖'Iԭ՝ (CƯmA ;S5y[ڭ9&8AY<Џ:ДB+A{ZXz7gGO=W_=kPGEHgxf=f}e=dcb]dsE}=Ea=a}`_=__`T9(g;EGEEE3Ev ~0@?pBl~X>XaH>D5>ANW~l  'w~AL?G?O??E AFA{_CF\¹?c?gɵ???@`tͅk4p`  Ap)1C%V܈QG!E2Ԩdȅ|0&L-3E{v 3%̙ Q]i!M-qsЖ@ JӤMvM&ZvբNǺ+3׉gyURoۯtYe xͻs KX1VH'[7ٴ}M͛92'?&ːt`(Q:afukׯaǖ=vmmֽnܽnpǍG9ϡVձ;lʔ>vg?6}-_|:+g$bA9@=+,0 5C8pEԮDH<7K-E{Fd7t?H؄r"IsI|rB)RJ+IJI-qD2L.LǬL3l/M5K#ߜKlF;F=sH?}tGAS9MTEmG!TI)K1TM9OA UQI-SQMUUYmWaUYi[q͵Ar_ Va-RW@I6Z4fB|`HZjcU!MUZAqeY]@DJZyȀw\L`&d X .@`9Ү+8c>@@J@X\ŤWR]-0 cqv`Z衉.RIna zjzAzkmQKKy [EDaI3{~lVVaU9@vqT` /QO}RFD"g%v|Z%ҝZ"% [D\C5ؕj!|q/VomLڷ^B>kLiXlC- t җ Ts_111d@Њ{`p Aj-H?/2a_&HwB!EܔJ9nњs@!z(G'if(HQR&+uKaSΔ5MqS=OEI>\E5PjԔJDX,hۺxa!4VQ]B0AUY2Dҹ'Q*׹Hu|0`9#(!e/2V e5h b1}Gc@@%2ı iJ{ZL,gjP!L\Vin}Nss-Y$ uBɏ4n&"dim r`⚥$ %nrN05~@bBonK*v vO)r8ǯYXy֡DN~H s4c$@!1XQ1jp E~]%Kz!9*ݭx{݃1S" `qcVef$3 ?33UȨ[ 7;O<,<4E&4 9LiLpk,x:áj A‫{W`؟-+(Z\]wbsȞ-CzmF'lܯiS&ѝnuvoyϛm:CtUwt(*9d> Gt /A Hi^,he-_t۸qwT89s-^ޢxɃǞ'5OB =U'=O` =ut;Vnum:w^rS]msN_Lk;H%!G\wߊ39@C#$.E:yя7Qzկ)`{q{=k{~|/g|Go}O_>~_o~zO~_gw~`_af˯0dO hoP-00o9><0j uAM!p/ dOI0 HAYNabo eal@ !up g!Yb !@z0 g0@ Pa`F Pp0ۏ0f!d/fA0tam p '0P `g@a  C0t`PL \0p 0 P1O/c0b!v f A `O  1aOQYq 1/ k/Oc-ao P"%O#9?2<2$G#O `A P"`cqIQQ j&11Q[ & i!m`a%'O Pf2rrl)!  ! 0#pb !,r-R 2Ԓ-R/R.r,.//=..Rs0. 1 3/0%21#33319 &1 34C2=s3245s5+5G3Q1u4e6;s6 S5c3m4]33M6{35a2}84S4831\2(e(u*y9q98:y::1v'3/1 80s<#=CkB1B4294 Gc1-Y.9ٹ.BٝY09$깟ۙYٜ ١9zY'4㢧+4z+s¢;YCZGzKZ=:QJّMؘ{٧{zz%‘Nك dةCs#Qx:٪CQźZCzDezߚگQzC ;:!%;84{8q匔Nxs]E ؅*@تX;~ VX¸EP襵4;x'T@k`ƶ-6(+fG+6Tk~V6'b۴"͒[&۴$kpXn͐˂,&L $4٧oAܒK>E~IMQ>f“~Xa^&6mfyr_ύq&"~Rs+.顾^ (=^&w깾>~ɾ>8^vlQo{[_{sƠ)^Qxqͽ ;  kWf+ǹYK"/K`|ʕKi.>?9< ݼ\ǢG +=XÌh]θlJOɆ_^"HB܍] v r?}Qka} μ?@L <0… :|1ĉ+Z1ƍ;z2ED<2ʕ,[|Y9)3H2'A;sDȳ ID=4ҥL9PS14U |4ج[^Zصlۺ} 7ܹtڽ7޽| 8 >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;ݼ{ <ċ?<̛;=ԫ[=ܻ{>˛?>ۻ?ۿ?`H`` .`>aNH;aaCa~;|N;*F"X*aHc6.Έ%SNd,c=(dJFHd%rKNI9؎"Te ;efg!N;v!,'('''''''''''''()*++)(%#%'%" # ) + , 0356666553/++,/4;#D%K&L&M'M'L'K'J'F'F'F(F(F#)F$*F#)F (F(E'E#)=)-7.0112335926>08C/8I.6N-6P,6P*6R(9W$;\!>a$Bd'Gh(Km(Qt*Uy*X{,Z{.a~2k4p4q5p6o6n9j;g=e=b=]?W|@UyBRuCOoFMjGJjGIkGHkGGiGFiFEgCCg@Ag==d<=_AFNDKTJIXKR\NW]P\^T[^ZY__WabQcfHek>em8fo4gq2hq.hr(hs#fv"_#J)21212212222222222222;rACCDHIIGECBBAAA@@@????????C H*\ȰÇ#JHŋ3jȱǏ nɓ(S\ɲ˗0cʜI͛8s̹$ϟ@ JѣH*]ЧO BJիXjNNJmHrl׳hӪ]˶[f3ǧv:ܷ Lez X"P|옱@/&ya؃MV9j\pЋr ҵB،^sٻe|Mȓ+_qvqۭ n7x½[&h'첒{:\l̝c y:˟?WZdCyWu⍔vETSUH߅f@wSqE&ރegaXy{RQu HЋؠv)Dn#멆IdA7`T6Rv)(Wɘc'&mel{ $T. wNޒ^f$z7ݞ1c~Uz㛔Vj饦Ciy}h^.e}~'j]*YÕd^fYJ.ȧS>F+i'v^+hNu&ԦV)ښftpfƘvE5pG,ug?<uK ,p(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` HF-ve;@5 ׸@6dxHGv" HB AD#" ?Ғ&&6yIfc$9EZ O AH4^Ҏ|$(JWn(iyI^%t-wbAn)ʕ [3.؏~0t ar zWFacֽ4~8@L"+ۃ%PL*[X.{]2L2gYfNӌ5gL6r3f=g~ȕdv`N dC#Zѐf2H[zC/ LoӉ4-C:ԧNUibL4^=<<8K7p;D * WZ5uk%MYFp>;8)Gp2{L6EY6Q^[&e%\ع F;БulSN?p9%\ژ3DPj(1dl񈃒n]؆s%jPÒ 3<ރ{x z(?奬 gCW!x[?_rI%~z%ݱG߁G0}૞/|-ꧾ Gaicb~r#^{`!zC7SJqpsjJ~dx|dXm{}}k6w J Vp"ww{ *dd·d藀Jex-1x5xweBGiq&P{4hIJp~KfxGiRExW~S\H_iawddXhehi?mrt8Xs n0epVu޷1g|eq؋6{#g tpwKƌP&(pR6XM؍荦،n(ehAhȎkfQVFȋhX8WWv {jdhn'u׉H&+tp p tp:td><99Y8I=GH- hkP{XsddèdF a?IJك(HVd[]d_ceJhy䈕P6W(L)iM闣ɖ Zitew|ٔh ٚxɚ9q0ɑqɐ d隴y}Y|7iKƜgƙ {im(kqЛWYYQ@99 ' {Pȇ6T ~j+ l؇J 0PdxpLfYd K ldsKFޗLR6QfX : KQ&PFMh56M&99WmX DzuJeWNj'r'hny7O9U Ќ׍f'n miܰHGZFzp9pPx6{|wDڤ{PX_ ʨwp؍ǂ:Zp*J{p|j|Jtmvj*zUڨ7 l vnjZs ep 9HTZXxS֦Hڧ`ʉAkK6 |wkk k,*s3Y ,&DF|LlNнR \ -,dLgf nLlҀZLq yLp;ǀ,~L{lybF vܽƑƓƕ|ƗLƙȀȝğLܛŐ- ? -ʫʪܽ,˰ʴ9ʵ-˯˺<̷l[L̿̽ ϼ֜ɬ͌ӌ ,ξ ζl|,x\ʌϳ l|=\< ׬ \]LҌ-m%*M .&=16-u\w`L]lROLJG D,)pB}ʡܿ[] _ a-cMegi Z ׼@Tuw-yk}-{ƒ{]ā-؋ƍ ّM=•-—]ؓMٝ-}¡ ¥mا՝}ؒx ۱ ۩²=۫]cٮƲe| }ܭٯݴܧ] = 4̽/ ]=Vπ M.Mt. &>nɝ k,*~ ᤼;9=8A=,A>>n4 ;)pnlegTxomnd-n]LN>薞䏞~}畞8w>}~oA m NNMW& #!>˾4 r 3폭ؾο>ֿMޞn.o  d_c@рB@ K 0#O-d Ҁ 02?4:/5/8oA; ;_/p HOAJQASOUAWYA[]_ aAcOe`@ks.?P3@P |?] 񯑁CK0}?QOCkP $P &$p0 f 9!uM A@ p_YY , _Ba0?oO1O@0Q  o  __OWJ@ DPB >QD-^ĘQF=~XɐJCo@p;x@g bң+UQ? 2M`/RV&Pe͞EVZmݾH $d`%T b呪R |aM?eH/,pǝ=#I$y5@ fi=1/ڼƝ[n޽}x\▏:Ne%L,yg<;㼏bN9C w fcւ{ۿ<"3g:ACOX0C(1#‚B /0C RN%:.2#'x (CqO eƬ6đ&ЙhGl$6|, 1I%dfK ft*(FXāP@JĒ -237 pJCBPH'D6(-B 3p|,n3RI'҇zOSơ ςe8LSg BդBUUr]R&)4=*%pj5^L |6ZiBr IZV[o7\q%\sE7]ue]w߅7^y祷^{7_}_8`&`ш^x ŗ8b' Cb7c8>&dOF#E֔_9fc~ o9g ~ѝ:hӥF䢇F:iԄ|f:jj:kk;l&lF;mf틸u;q np[_xyi^E)ցЄ i>"~9|0}Hg &--v%SHBt"!h"+dt.g=$.'D$%IYU%Hcb#iЋUDr )磢 EedMτa(EƱw9L F<` أ{B&3Jӟe8JH֛fB=B> TuL8Q$ F5*3Kf,wPO|ug/$- s2JT)j7;e9 *G@S$p\|#*16T6@ZT)xF͠W}⌖SEVխok\:Wծwk^WկEP&ElۢPTRldfJֲcYvjPD;=;ZmiUڎIh,emleĀmnu[ַnp;\׸\.7ͅnt;]V׺Ůt]v׻v;^75oz՛^׽=/|;_궗}_ꗿ=C_6p`7 vpa 7D9*\` o0A'Fq{ANsI5|qcݮsʍ:$эF萮\ 4,Eד EJ8={F5z9H=VV\o9z5,qF}ԭ5\S}HW~A|܏ j>̥2 *~T<2B/m@xy[ˇ}+?ʧ/zC!o{׳󹇽 I{_7?걯|ٝ0'}NIn/%>#޵ÑLЃ:c? ?={;<Ëۣ@ ̾>l@@+:?抂sP[.抿|Ӿ3q+7i;DS9 9!9"8#7c.#Rd:B](5*)#;ʷ}.}K8/\.5@4<4i@7tkuBBzCywCvۮv6#S.k[..8A8BeS.-.EFD*D˱?\:5"x6jP3t!BF|?A\+7;NEMKEJ s@zHx3B?5H\W:/Dfܳh<3n\Ĵ庳QDrX,38X2MXGJF`zKژ\ؚBmX&Nr ֢`,`-{zc:&Q+% JJ{E f-fx,!F$V%f&v'()*b+obmjbjqQ+Šjs!xJ@WHW 8  י:cYi86zc\1<> a俩yxSdHE+OyPcpDGcOvB)B*c p@ 8)Xe =6gLx5>Ay[8A( I P es `4pgst{vAQz Ygpg9uŸPPehs6#>)m:*y('g ".0HJRoI&jiphurڋNH!S1j@jjhj[i1Bg jdp#Jhhߨj^>΍)R*2ih3bފEpҜ !b @Ti?~Vt0l4JYX(6'V $2eflZl2tʾpn(#/Y0ݹUꎿFЌ~ l¦*fP!("3"nh kr#YVz$0$5Sv.yTTM foϨV~^ oqo󞁟@oW*>S~"ToMpG[7%Q` XFF !N q qS"ng.@6Fq@q '?"qHFm&w'&firD)ވ ("*]. 7'.m"HYmN1᧎d9TPQos&(A/B?l:F?o鐚P Ft,\ JA)OP ,wi415t()!uXG7Yr(k6Ǎ;%>@_CIVpa6~c))hiXΪ؝jvbg6p*s_L*u)qqwk {BkST㩚n~wVn_2)]9)b" `qp)q0gxfy 01'7GWgRz!8&Ɗ7#z 7mg_}LKrYR%'\W祺ߤ%o,'|{,ķ`TLJȗɧʷ͏|ylϯѧ}З Di4nnqw*G'f}7}2yܾ}}~a}}feeKeDFڟMag Ǣ7M#Qa:_'g).:mfz`P A.,aˆ'RhC2<8!ȇ1,iR"[ $Qj)1˗Y ҥ'ϓBZ 92bHIv$)ժ*֬B!.<Qb5j-[Fb.Uk-[^.^\ƥ6l޷^viXT#ˉgx qhϜXnՓL6{ EK\ɹO&\7á*uˉ6w,ӇJ0ۇn.oya?Ne*kB~GN5*r'c֝X DU JXR s!0̴P!nh!&av(x+z"hb)"488c6J4#@:z($EE&#ANȤDYYB!Y"TUY%^WAm&[C*kfaP< ;IzT{[u%ץőZ衼%b1( ܤ:zke馡u~i NvtJQ5 E®ܧ)lj͙WJ9-^Zʭb띶ZZY}?rFP_lѓ@UlUS/ /2muvW 60 {Dq]huĂЊL.{@P 7Q̢Z(!ijʏ0SS͜W;X_]946i6m6q=7u}7y7}7 >8~8+8;8K>9[~9hW Aq9衋>:饃4T#m:>;I?L;Wo^+OV>=c><_ ȳ=z܃e,~>>??@HS(< 2| #( R 3A C&fԃeɑ!Vގe) yPfX:0rwb9uP1v +̘1F i6P67r y8nQ79Q9: A@ *٣A>I>J?P@@ސ>$?" BUCCb5C>dE6E*dF"FdGGdHHc?bcDBdJJcKRKdLLdMM$OO$PP%plN5p Q.%S6$%(BdSVU^@B(BA`W~%XRV2Tވ Z. ڤ%[eeZ \eX%܌eAp[MM_ȝ`%c ^>T6dN&ecFfeffnf@Dph&ii&jj&kk&lƦl&mO[\n&o%n&pppq r.'\&s>s:'tNqJ'u^gpZ'vnnj'w~ɜE\'yw'znLzx{V{`'}'}bv 8'u'"I@A/B/8zz2'6r>(V焢Vp^(fA88C !W303(BW(w(vn&is2u.gFA! È>KWDM0 NNO@(N)UĎzE5iriҩr)&iA4:0LpA|K06 h66@ czTJI{3M4nK4Ps CB <!B-ʉ [A46"8r")FNllLJEnR{ |H7?>GO>;~tWcЯOo>OPCt~>gcP꿾nW>3P]wm.>??dN:Q';3$>Z=b1MNMc+ #G{d9o!}??coo2@2p`t,HBj\B0D1fԸ#PC 7R!:0#D5 UG7q^8HPyHz@`tiqV`9ǟWf EM!j1jYgEm۱nM+nݍj歋Wo_d +WHar݆4Ү[9^,z22~$( #VȗH2C!*/1nzKWreUk&ʕ`X3tȯgY]{;:YđvA7r$gؑ cDeC׿O^Ih=+>.obAS-A܏ SN9"h&!>b'XlQE"dk]FlQut! @(tH-V({̽lJkR)WXA ?B/'# A'1!(C@bf\ TP `* MTQ*2E%jHL-TJ!@ RNCSL?ETH uTMYYm֏nU5^kU`_6V͕Q{mvc]ZadW]ZnW5seDOu]vaM-tō]s A JrKJ 0i TpWT+L``)250dXxޑRS%z糓8^p+.6U{un!ǤbZ^ )8=THkϽcqk&h 9 B'l.z;!0ӹ!꾦>5Jqo[D_*wUN! }JCi (fp5B+U4p 6lb_#EO+1Ebx)n_c}ޘ@e\8Kqmld!XJ2\e-3y[~rd(drlf.S9Af8ynl|7Y`s<2[2-gAyX21[xюnMHCǓq1jGwԟƨE6SҲi4hȘO-pq-`vؽ-nm^zrd۫O Ͷ}mj{ p;--kxvިwi-|vߜwfٸ@ 8c؂v~mوuķjO񌮏e7'ʳmw; Nvu?` 0Bxg?y@x/~/xO":/BjTAzя7Qzկwa{Ϟ$!Q{o=JwR#?!8F"J'3 !*qTB$ ArW|!+,p~~`#qGV}SǾUd&;Z4_>Vo>>3?s???4@t@ @I 4A.4B%,'4C5iCA4DB}DEDQtLTRE]ԜL^tFi갮FutGyG}G4HtHHH4ItIܘT',*!ꪮ"K 5tnR#K=IT"49L_KA)%" 9_ ԓ N9ҔLqP_B&ϔL KQ+L4L4J4RQUS9U0NQ9SGc*x6P; 2SnRELGuVq R3|5X;5T)U.uM;V3V=X}YITE 1)Mc5 elUZEU53ϢYZsUSXoKY5Y?IEBK5^=):QKQVVa=uuSTWW62Z+(,]ncAb'LcEc-+uEdU B R %#_!$C)d_c]b;V5MuP5]c.KvY6Ji]uij?Zq,R,DZU֎a60BoW%jUmjkMMXW%vS+033+(S4Ͱ42cVn=6Wixo%^Yʭti5TUio_:S6qStSO3:OOu uv 5vsS7Wywzzz7{w{{{CJ |͗Fݖyη}[4}/vw~T|5O~^L7e^wi YD!x$ ؀n+8 4}V=x@'IMQ8UxY]odxixCq8uxy}Xo8xxX88xxy؋xX~،Ѹ8{e8X㘏هۘYyݸ(نTx8ʯA`j^a"yocNaTex r[w;{ƗCG\O{A٣0\dX9Cw8ٗ] z뛾|ʿ[\Ͻ=>~!^%E^Z[yM>Kq^养5`jkugY|_O]g]=I^O潞w>Q^]Q>ߑmցGbi8^滺^˞qHΣa [E|;I {e/?śI/;u_cXk-?E{q8EK}[|sWy[8ֽ?ǟʡׅy%{+<埽۞13pP Dĉ+Zx1@l:zg 7n Hl&\ÈkڼYQ@v!%,y2ʖ(dpDLbݝ&q:%aA$݌336㌺u$Id~u3<tb+עQ%J8ņel6 iehy  :a9i&a)ns6U5w駛Zd#"h6Z(~>*)V*$j楜Rvh[~J'^zjf:׫%|:^ZT+7+k,E&+Ѳ&lek^mnm~ nKn枋nn oKoދoo pLpn/fD5T@OLq_qoq r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxx/x?yONy_yoyz袏Nz馟zꪯz뮿{N{ߎ{c{n.|gy?CO}cr|"zO>͗~K?8"܋oq@!~,(((((('''''''''''%#"!!!!!!"#$&'( * * * * + + , , , - . /02455667666677653/-,++/16%A'E'E'E'E'C'B'C'E(E(F'E(F(F'F'F'F'F(J)L)L(L(L(L,K-K,K+L-M.L$0K*/H-/F./C/18.21.20-11.2503<06@08F/8J-6O-6P-6P/7O/CL1UF1nB25231212121222222222222222222222c;CHOSjțǾH*\ȰÇ#JHŋ3jȱǏ Cfd'ɓ(S\ɲ˗0cʜI͛8Eɳϟ@ JѣHnܩӧPJJիv$ׯ`ÊKlԮD١ŨZpʝK]mU7(Zz+|È+^ j5Lm)DdJmƠCM]#LyWdNk[^șΦ NTc]0nas:7 1zvѕO1yޛ9o^y/~]'Ywcz 6>VoIxnٷ uF}Vvxu(_}0jo Fh8 }&"B6!&S չ$5%e!H`)a{| =aK^H~/Rw{٥g)栄jhQoR dyV}9oYm&Z_f韵Qڢj~zjr ꪬҊ)≲x*kfk"Aآ^&vh|6ʥ Y y$Jk͖k~Djd-Ej ]FY%ܕFA fZYaG,k[0 D K0#[$,O+&,^,ԺnL<7D%OlL7NG-;+MXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.z` H2hL6pH:x̣>~ IBL"YHA2򑐌$!IZdd%3Nrr (59R<*KU|,5E؅'fKCҏe/_1L6 d*st3yi&3&5bv,H z3pF$NJ<Ï/JGjb!ͱuXG:tc< OJ<"($?qC- YfB}G3!Y:L)'*Oz*ԡh?GbTGA zd4$Jr.F4`}δeGF7:R@}P~tF)&AN۰TqGvԪ#ͪ+IP*^gKyGөY tIMGRD(vaH{Sf}jMXB~tkG[aJl-y.Xi:+tzc)BPi9Gke7>:νmnaFcpKܴ&.JZhfzq*6[2}oHJ:p;]^7nwaAw)\ 8.~]04&X3pum6o "}Ey &.m=hTmo9,_v6q<,,F)ɉu/d:> r\ݒa#{f9T&qP;f?Y8+2Ĭ$n7lh<'Ɍ *Wt?zv9HzuRI}f@y}vtVpY9ojWW(_ӽưYyXhr R@0\Su]:oۀ/fq]FU0$o(/<`K[ٹ6wZB{6 jP|Qp{/#NW~̸9/ '=JU 8ϹYlJ(6.nBZGwvHYl?x=GiUxlQNC;>1}l݆x'|xk6+\. Hh۝7|A}΄m7/sMx e;ڏOG=sկp?._~Q=ů~?r.Ͼ}~x~'8~~ x'HSP\N@rvwy[w_]v׀`ZXGZ+maVhgj `WW'fwh} ppv4 G H hTIPs3G@(FuIGKXbQ ] S}V~Xc}Pׅ_\,wc~eHHh(qQwXp6fog'etE?V_jHb .H ZZ gg@pE @ien]j؉ @:>*Dz6jH3#L( RZ!JVZڥ^`U:dfzKjʣpe plڦCzxznaڧz:8jZZ $Z  qZ*SʩL:AjDZNZ0j:jzJÚZvʬ:Z *Jj/zz660zw抡ʮꚮygZ60z`گ Zz ;_JK[ ˱;% +)+.+K-, &3 :K26C˳ ۳B1J˰N[QL{-{P7X;I۴^Z;K_{]+fkT+lke@KO۶({ zz+z먅k;{ ʷ1~{xʹmj kKSuj*d jJ,*^]j+ʻk" VJ-ɫ=ʼ +[*WJ׋j& ʽh*[*:[;K۾kJK{!{ ޛ@j|E KPD@ºz+- ĭ 9 +Ą QȚȺ<>l~)*hfL ajV[Kr|<|q>/#> .~=n<.0ns̎mRn~0㝰N+.t>>1j   ~ 0pOqz> n `Q۪]>/_T-`8On=?S DU ~NpLŮ-?؋d_fhj#4P<<rCtwCnP{C<M5D oCX?_o@az/6AUCmX/]8(G_v^?1^=돠== o졟J @O  Fvv?ϪoGQDi:J0)adDoz&8))"… Ҥ2}̬6&5P0?=}!0i@s޴VXe͞EVZmݾ [ܜr!tغ-z{vLV)ԨO%j֭~ȹ$gҰX2;/,LF!3X+~cȇhArWōG\rhgգvvݟ7DPGA[nDwp?8ҩKgx[.5Qs?p)`?<$,4Bal*ં+vFdC1EWdE|fbeƆpq}l;#i軁F&utS zl.,)6@қt& G5uD3AzjcŠJN:%:EEeQGE N\I;/Z)BmL#*2|+E'K@V£R ?P%PDY+Zk6JUw,V<E a,XS!Zi- G%h,!o6$5K(çN!qFl K6dG&K:XP^(uv`X48cTA+S.X/+X5BITkj`6sll!߆;n离nnoy矇>pay>{_?| ]&?}g?G}秿~?`8@ЀD`h)4Ёt 8A VЂ`5 JaEBЄ'D!KB+ta eІ uXЇ-Bh0CDD&bpME*JaUC ,"]L8D12-HYЃXF9ꐎu8C=1$'z O,nD1~A7L,:aD젤@Kll''I>d5O)aA2"(xJ(G}"hD1U6Q<39BiN3մb2@YqǑqĆ=nh8/Hsl`;Nw2d&8`rRh)ORC: :"FR2iFfԇ4iQRt+u)%MP%( # :&U uSBSԢ3%ӟN4F8hĢs=b! ]nHGT*ӹ| aW #+()_pj8AVC U&vQbW J$C')@26b#YJeb|kl8#GCmo^ʣ!oy5nn+ ,.s+]u\R7wśݮt;\)w%/z^Wx;_7un.w\u0`u|';؛B%e Wp\aDC&A åTEw 1}|F-vl#>汐 d>׳0Tf@7xƙݱbe*.cB̏b ^*yv*c V 0[ق6b fAPEǣ7δ!iO%r,c&F]j:67B6=`FU6h]בŊGXԃl jMt h)&}5qDuӭuG~75[鉧DF%|X =nvȵ;ocҔofYB`ܒ `}?S]%a4>5Sk4_~ꙷ&9KĜ5>͡C9Г[8o>ww;  Wy|9fЃ~(x7qxWr+<V@}}I}5}Ud}e4(s}u̡ }?|G~|7χ~@A~}w?~?~WG??ǟG?>>$<@d@C \@ @ @ @D)(H@dAsA?A[ffuA uX)0AA`1* B/0#23DS5>7俅8d(!,و  )@X AlC88BD?HDI$AJDK|CD#@RIB*) / BD'AM+,z(YZ|NA\?]E^ @_E`t -EH곐XAEt(AWtCXE,\Bq|i4IG8wXGts, xuyGwGG?z}G}~ǂ0ȀvdžDŽGGHH\ȋ|H<HȏHGq I\GȓȂ IdIȖ LIDȒɛɎɗɌ$!a5vʢ\ɘTIIlʑ$F#Ib0 AJɌJJHYFh,a #iB$ɰLKJrmE%QQ];~ "ц`&u1(Mh',%(5R0"MB9F*56u0812҆]:9TAU1TS#Sf8[ITiR(}JOUKQ$UһPETQUuWVYUZ\5[^U_au<cdefg;ӣNi"T )X&DcuE Ɛ opWreŽtU!Gn WIW(W{uu}׆I(s|}vn "IpXHKQX؈EBz5ׁ(LI8Qqِ ;Ȗ+"lHMxc#c6Cc'.9LKB7.]EdgctJtlYFק<. 9(j<X]^?(T `s@w^EfefgV=dv`5܇eggfEI܆8fŽtN]rfx&t/5PN9(hTgDibk;AwAcagY6]gfvVzhh?(6vb16@hei^uޓ {if ߛUnf6ꬸjW 1^5=[ea-*sG) {pVy [~ O.eקf nmIG`lyVZ}՘ƞg.l[Z!Xۜm˞lm~░b)ٍoXV h~^&&6FVfvo` Իo&GRX(,'7p p,|aetp8p4J 񡄒 ' _gqVpz% !'"7#=V%7&'()N 6YꚍrPYN?&nyb2/~Y6707?9>sYsXQXeXeَ€a_Q?Oar0k&t?j_G7rNu)VkRi|vq\΍]}i ]WWKM uLuSshWho? 8 x ' :ؠJ8!Zx!jHaz!!~ء%x$"+"1(#5Z9҈=ȣAHQ@:؉1dF6LJ頓 B9%RT)ŕYJe^. fb2SlY-B=q2ء%u Kd#1a9c΃qMJ QBG})%D .`8:qD:!z'|vi2`1IkE7x.4$. (6(BAXk4K6L6fIu"Mw%-'γ -A,H(OIT~l*աP a<"t8ʱQ+I U$f0]Y;MҖE|rJ42tm2lZV[^Q㣃Ml~S1ɱAJۄ9H3t?] l P%L"AH0)O Tm+Lc0CIh1Q)x.+_ W4)M_jSthEQӂJ騃pI2j)Y ԒNuriN=b|BR *DUBq%Y 6H Ziyte]F_˟ ּZױeP̢ CnH^G0EּbbVmP4lM~؄E(cJK:d۹?\RHǭZGr,|EiQ׬ǽnvEl[@:Z\%z JAݤ.~{ic|[7su5K:AR sB +vHR[6rލG)\I.]asf8:DTkc)Hrv⃮KN!"cXA(J/~k *#Y=Wk"8/HQ#P 1ϕ=e)z6[(Ə1l3"EP*Z#u8M "RZbue]}!XԵioi]"测#a'ʵ{l!{ns]$"aжlm'6-lqܪumuv̭kyߚ޵]Z7.;EAbA}/8#.7,ps q?<&?9rgq8c.<,49m,Sӯ>[_~&XSD5a?_?  --  (@A( 0`L(p8:D9 j *_E_@C=0@ ) `5a) @&``)C2\ e7`x79n r. a!a&!!Ad "rcA'l(F )$n9h@h9z!aa= "("i( ":lAkAAA'):F2t)Ha=bE$@@:try"lD;;6j#7fKmuc8~7;6b::#;:<~c; 98JD?c9c=#A@#T^eY b|`k,/c"/^1 #^8HYb%PQL.XXdG$Rd`֤`$D`B,5"jfrzghnQ@eA'N(6)NiƧZ `2ni))S)--B* ,\d$>pB*V 'Znу g`pA^&**ƪ*֪*檮**Ư+TllBC|&A<+9(2dB&(A|^Au|F> [C2, $<(lE,e,RVR!Cd1Bt!Aa֫f+AA(2@ODHkAxlNJ,*/B00B79ACjE,0dPlx:D#,BD"AP"A (ABå2Dq|l^ButmSEjAҚBVX&AĂ@lAX nA0m^.-6-: **d*-Ȇ֒츊QbE*lk:pW~ێr@m{_n.2D@@*D*C2t+N(F^A66[z JB]9 >FVm-AoӚAA<2 E-N^z-זS"FAbF&0N/EoAlӊC#DLl-º *obo oST!A&lBtBN`>0zVHDqK #H6D:kF'D^A&.f-F7E1k%pBDok"Cb1IDZ1ױ111S_]c!2dx!/' 32$߱ CD2&1f2'1j'ӂc(ީ2*+2,Dz,2-ײ-2..2/220S3ԧ*Fj Dk+siF%AA03*DK2|.^c-v,-ZaAˆA0D8/6F24klBV!#,̞/l:3䲳/ c"ñ,l+BmoAFB3̞tmDtACE?6e9naRdB_D,A0I+TKVua[WANohO4a?.p"Pu. ,LgA0B@ Ktl_WA'lexBmSWpvKqw[A_X2S(DhO~31poehktIA$8bSdmDltx; 3㇆o888©8cDgĸ j8I8\q9wp:F'9a*?Dq:9W_9go9w9TژysșyKʚyʛyy$빾Zz][kN97:IQJ KRʰKP -K٤aJtW1P-شh <:K:4cЂlI-֣ySX_ LXLH`L-DM_s94נW HA9Hp {Ђ,; {{<{1d%Pe9>ђy)X$<P8Ψڅ9Cͻ(\,BEZ+Gf?¸5<G3LQH5f9<-SyV& ÙM=eH!7YxĎ-ZMTNNTTP=USTO׋}1gSڷ=ܟ=AITOy=;}Pѽ~T} ~UݗK~KNk>+~N=>O,EIؼ 1.-<\I>C>%M9OT˒3$p4)UH9.iVC/U@9:̃UOVh]V#-VhOkj}H e :7ȟ:@ؒ"@&TaC!B,bE1cG7~9RdH'QLeÂn:H~mZp( D7n PD[h@:lJτF h֠I?N˩%c 7B)BZŠj)؊~ջ=zf+K])MV1bwGXre-ggYGyg'`dلر3wl vfQ#\ZaFwF`P:Ѝ'R#,xu˜o߬>=fݿW_ad*~Ͽ$C \@p% (1 ʐCKYDN\qc?eƌB-#}\/ ıH#,I%t)'R)+R-/ S1,3LS5l7S9;S=? tb+MTEmG!TI)ݓz2*OA UQI-@qa/`9WaUYi4/%bZ_ Va-7qgXgVi Uko W\(W(`XWumw=CYz{W}X .NXn!X)ۋ1؏9AYIxQNYS>adeiYgA}m6grde:dΚc:[695&lfyPvzfޛo[Op ?<o:ʷ1.2.f-e͗Μs=\EtOԧTq}3oщs)5˩| +6)[`9\nn=%~ `n,Y9)ͫ r+Cܔũd~Yl n&߾pmJOιtsuk'߉wmZ9O{-g?}mc+(✖whCP24Dхf4+R%5IQRR VSΔJUA=iQE5ꘒlMuSrU՚ /VVVS݇ƚV`-݇ Εu]W}_X5aX.`H)=9 ,Z/(8kDd@0aH@Ayġuc/8P9,a4^J{ԮKlgt&6&'NBs\Գ-TZZ7x9sL ]"=8S/BhS~0 7tu0ЉE<`/9prlO^"짲60x} A|@$nTҧ̼/^u㽘C xB+?{o24@9/R eXP&td0yUÖ;9tg<Ȓ]fƝnlg-C tP@GGД˥7d8E۾H[ԥ&Q].u./`9&Q2&s0h6%[bKCvƔl6 YnfvxIdw^ȭh[H1ro w֎NG̠Krضl[Y8A6XJ~΋ᓓUgIKPr|_xys'BN (,,lii=Ko{Vr+g{C*t?={ӡ.Cq ͋}.k+_QV6kNi{vn4Vix8]>Sz)g?4@t@ @ @4AtAoA!A B)B-B14C5tC9B'CA4DEtDCCIDQ4E;DUE]EYEetFWFmF14FqtGmTGyG]GtHWTJ6TH4IAII3ItvJJAJJKKTCLKIL4MGhBi/fABaC>,a!BaҡM/tBt7C&.TP A;aAQ+tB3XQ46(T4B-P4ScS=B?JTSuOuPauduS;V!tBt/BMUWY5X/5V5VN5*YyA<UAՁچUE]Aa^5V5_4[MG<58!vBu\˕B_SѡZ=uJ!nAoN.Jvx[/5,!d!`AA!Qcdtd5tz5BYA]6VifCwAW*!<gd}5e)g!huhD7ivKjAjQVejvfM,BVeɖj}e_6k_mAAK!4zhAXa]Qkh!^^hh6L?LtT+l#tp p6qqtJa:VAAEr類F飾 ~빾>~ɾ͜MڳۺQ]َY'qױ3mZ7o辣SzwJk-rxoֳ ._u`sh~UGm-{=tFhyIh]tU)PeV !0}XhMA a{][ QG$xXVhcJLye%G]~ bz9fmi&h٥J<#fsJF'b59 ATG] jBR0S RХJj暠%Lj*RJԪ&SQ0tbb S ܩ'9Dα8mQ"9$@N ݥjI)Hi㞋b8.M+Ӽd6{j<"I@L0VcT) K$;<9S;"O+1S i/,|T0_$lsQ9'[ʇSa\tSM4Q;5U{5Y5JwtGcM&iM3k}&~]jH3;36=30 2ҍx/x?yONy_yoyz袏Nz馟zꪯz뮿{N{N ~ |O||/|?}OO}_}o}~O~柏~~Oߏ p,*p lJp/ jp?p$, Op,l _p4 op< qD,$*qLl(JqT,jq\0qd,#4qlld#qd;cG;q #?1l#xDqs|%/H*'I2,e(IT Ҕl% Q/ !,1111111111111111111111111111111111111121212121212121211112121212121212121212121212121212121212121111111111('''(('&%$#""!!!"#&''''''(((((,-, , , , - /36665555555553.,++,.0 3!6#:%>&A'D'E'F'F(F'F&G)K*L)L)J(F'F(E(F!)F!)E&)D$*@$,;+26+3=,5J,6N+6N'8S":\;a$;c3;c;;c;;c;;c;;c<?ABDA?????@@@@@?@@a݆ݫ̿hURQQQQQ-H*\ȰÇ#JHŋ3jȱǏ Cf$"ɓ(S\ɲ˗0cʜI͛8Eɳϟ@ JѣHnܩӧPJJիv$ׯ`ÊKlԮDŨZpʝK]mU7(Zz+|È+^ j5Ly#2-ƠCMn F&m?nVZ"חA޶ϲ >vgֲi#/دyͱk|ܭs"{N$-5 ;jɏQuۣ'hCd)xJ[|vEPmi5a}Mo,\ 6yZeHay2|̱w߃ZXa EPs]xPF)fd{o(_}Vg"nh$qܘl)Kb"tiJyi<X9ގkZ|Ns ~Pyzx݈M>xf馜&}ImZ-Y䘫(jm%wj뭸ʦ 8ll ѧyQHkF+mMk 6Kbmh^tn;i )vrҧU[gnf񊩗{Ϫ.ܿ 7p|B]Ygі Ըr,$Cǁa\,,O4<65@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZX̢.zӁH1ZhL6pTH:u#>Q~  AD"B2t$$')HIR|-Nґ eA)RLPR|%,;)Y^D$D6K[ⲗ%0#9V <&2+LS*}|&43^NӓҼOv5 p,g:mbZ6! -ќ}џ! PӠD7w Nc-޹J&ӢT:5Nӣ8EPq'zOҥ~$i7eM^Ӧ)4uL'VhO[Zj֯gc[%j%[vkk;ٶen!\߮ͬtuta\w.mZmsy\ע7ѥ|[݊Wƍ/}7]⊷䝯{7}e\߷FJ˨ՖZR1mv8fYXCuM@ 9bx7^UTؙ9ew󬧅rrO*W򒇼K7:Z޲0W4dƦ. k3e6SLv3:e;}c=R3%=IFσtHJ#b0ى tJ kX#Q{jf !ȇ>L1W@@ID3Lmea  BW$ֳ&q+'r^zW8MSBT[B*I1$@` ]t 5$ !VtA `O>aD$C yw Bo{$w}[ mH _&- q 9r;%@kkĻ ?0B4ssA-}5=" wϩu*P_n+ f϶@}qBW'ЊT7/nr:2KB2~ Tuם"yd2-ƜcL{ʁb{M{d|7D'V:PI_FRלj c{_d*LQdܙm.<**~wPax7:k} } ㇺ?s{"/HnGvfpptw~T7 Wq7z`~?RG~ .&pq a v~  `T b}k `|>hà ʠv37z+zCk3GF}Hhʆ wgj0ql n:Hiֆr8tXvxxzX 0Oe=rf$;4vg9ԇSU!B#Fc‘\ fVVVC3Fe}XC,V Q X3bWO8XxoFӋJ#FZ&rvmd(V]5b|jj,(y#` qkWp~ vgYoy0 Ѣnm_p 8r%g Ȅc8Gw~Jgx؁姏s!}t 8wQg'2 `ygl|pבgxti5Qs6y8ijWvi7+w?gx] k(2Nx{'FWF|O|pkG3`Vyyyw{fgz:0 1Pg$ @ ytB~39^i+ٗ!Q7u#1b>ilh,W2Qn~.hr1hr1:p%ykP}R8axhfr[yi+<@nCXt!i~ř<Ǚ3ٝ9x;Cey9eTD{xJ@dOyzy.y,W$]izzڠ=z /jc$ 991gɢ Ѣ5*6( ڞY};BA#!z>3*BH1FPW^Z_*a*RեťPc'IʣsG:DtʤO -8:jF )էX:KZuZ4*Pj9TWg w ꠋz.J*:EpAz:DЫj:ZzȚʺ* :zʬ՚ڭܺ⪭ZzzZ麮ڭ:گ;;[;Ǡ Āz:ˮો0 K;Z&k(,jPj` Ыp ` Ŋ ૯)E۫`@{>*DTJJj`j ` ` `z & p Ыw[ۯZ۷;;ZJ p c뫆 аJ IK Ы X˲z Ы{ʹ깱۹[[ K  < ;;p P$[P {[ ˷뮈ɺ܋߻* j 5'Ġ ; * 00ھ ;;<{櫹*+< t Ы@0 a¼Kj-:*, Z z̴þ{ ý *4V<Œ˼lÌ8[j A\͔L<˥˷ \ί|O<G,z٬Ѓ],u{,mMM 3<⌶þڼp?<* ,1jk7- Y<: [<͵OQ-SUmՃ k@þZ,\>3ͬb- d 7 !700 o:5ΠDAT,|DOhZjfՉkȉYzؕH*7ޤ@.Lr?1/Xjڨjء)^^ x.N$^ڠ#>]^3~>^~븞뚳FjU/*Xj+w荰7]Z췒s }pXoY p1W $q8sB9 r >cZ`dVxw<QG2 /1w Nz8{Sǖ{o)Frv$_dowqΚu+$1W™w pIR ? j<-*hjͰqIЙ))-JOS/22ac>gik m>o 8iuhPhmy!fs_TAFdood:Fh_cOLf P+ ~#}\fTk}/F\G&uFɟ͏7¿UZ`u ^%``_fe[O5`/Uu]f^Cy Z'Ab,x0B #x@ :}RHuPhaB(dʊ3grHRΖ3t('͔=zSGG眞E1!VɤCzRXjtYi~MX`DZPڭeX{3-۷Bc3߸+Yf: hCdMFZj֭]clڵm߮=n޽c\xpÍ\rř?Wtҩ_YfF(㐶ֱmٿG|ǿߤ|?`;E Nap>#dnB P p:q MmEmsd.FU:sl }!!H<IvTR&?|(E7&}2˃r+R02<.yKE-rMlO| $PCE4QEeQG4RI'RK/4SM7SO?5TQG%TSOE5CU_eUWaUVZkmWm5U^{6XaTdbUM 5XEeiF-GGSC~ɧe JFa jٽ6m֠p-\5(!}56KnW\r]v~Dza&!ׂ{>ׂtCZW7=ȕqxX䑥ed!1fw `>ƗO޼g` qUgB[*xOBN$5 J^6Ș0{HH{Sb l&ۂ j1D {N[ȿ-\c&p]-܏ɥ? ctqݶMiOO!Y=n3܂6[^|1_ SN1}l~uzv$T\ A٠|[s-|_g oHx>~ H>u; hJPH7BIyPQʛ`"=ZO 9=]C1A R+Y솴j,C<q)*C"D KbrEjƾر"8|crE4 )QkF$фtXDQf45G TPFS:C2I>pjȖ's@h#zDeGFR$0AMv$ At0Z.?KÝ]H S"RJ_`0IKj^ f8Yy^Ӓt#j^fR m8"i 0=fxlI - uZd*Xwǃt; C*zjI*G5R %I+hv.""S '?m@1Q!:6pC 9BYآ#U)EC8}00^h$Z= B CV\'WU_SkHVMaw׆!Rb3CcXBY qWr'GyUr/7wU.n 7y?6T!dBR1.I& yҕJ0q}3; B礸-wPfPVUg,G|=nɹW|'w U8F>nUEi|Y:ƣmjOdcbg%ߖgƯuJf&!h=iNBwۿ>{s-ݠ FRq7uw&]{y%ml/VU,i^׏[Kax.ѱ?~R27տ~?ƱpV<# !@ +;@Ksxسf2C{`S;[8|; | T'uW 6 |(]AsĥJ2ADVDCg@O <D~I @}| "Y{)*=l†.N3C#,4D7~6Cb*$O[bCvCZAl%黲,$A+5>DA`{ìaDs:\4 LJ|HB8TPDEDD TC4O$QCl8 D-=KE>^ żC,I/3B8lA\GFQj$jT?%dœD@(|IRBI?S Tcc0&0SfQ3uAx4TN OM7eRSX=UNA%TCMUSTYQ}[X[JUEN(PjC_ : NR@SR $W@VUT]NPi=RbD\SdVW4EtVWjVyz|pAXd M-yN]|5}~uXM$l]eVk=X{XWWVbW;UMUY4dNYWT0V6cX=Νچ}[P O5YMNmVo}Y~mخ5Y܄YX:5U5n5VV Yd\Ah[xluA[[=E[[B5N YX]`[N N=í\5\Wۉ=NeՒD]ZP]ܹ\]өZ[ ^X.EVTA [UUo܊Yݺ ^WT@W^eս]_XU4_HTU_ }T|Zc0C ^Vdp_`\L`uUSr S- > V]$_ ]N==aa.^UNC[A؄UW]\_\cNYcev`v`*bFN->,bc+,&c1buEuT_dW R01.><6-c=.]tc`@ANG>d4 E%ΉMޝ:IHGFcR`N~OP\TeK6e@>ZcJ7u]_ '~L2vd>fVS&e`i%]ba(fg.eZovf[.N]]NTseVrvgT6gVԐflbl^=NrNcouE^YxV"F#Ft] FmXMa5_хΞmtAp:.hUhF΍eh.ibhDΕ#m%i>Ι&_^.؞h,WIa~NWd]j>j}6DMNdX5LedT[dF_OֳԵ.SMS&܆5ifj\[Լ\Qkb4Mhu_¾kc@~iA\kǮ_e\Mm^]ldhtlj$Y1uW{]jM5A@QAPUg_Vn⏀%:FgXv Fr"Bn_iMҏPov~pn_nokR%W)]|5o7ngYo[^oc\pFn/ZW6NjR}FL q#7~3[ʛ>k 1?~; A1 oD GLoZh „ 2l!D#Rhbʼn7rȐ"GZeP-mdi#̘0_ʬi3"͛:wM@$jУJ)&]aӧRIR irZk]*+Abz٥h]˖۷@I̪T"ػ+pQg/fcv+P a%=9gziމD#-#1>7O޾`pą[! 2<ĵ[м`rؙ/Ws>] ݱ7p;G?uwvG|"8wI$ y7y`u g`ɧ wV~h z"8#y'b/b  _C#?(IR H(%h5fNZ)`[(U _Ygx&Gwg)hhld<蠅,'G&jfiiiF:ئKj٫ ٬ح* F&`f,^֥,bĬa,Vk-Sf-DвmZ%SEXmN.Y&Gkol"WOoOk ;0K<1[|1k1{1!<2%|2)2-21<35|39뼳ɵB6<4E}4I+06846CMT>tuU͂ 656Բҵ$2Ɉ]X$q5HO?؂Ѐ~8+8 aHT]:d3HK, iyc=^w; R-̔NR2ɀ;I[9H;BRx ?<d&6HN>ǂH{NشDo3-;LHߎx+Mx->??M[ki É>^"8s|HB chɱgE8~cث2fo3dDr5nu?$G eb2Q;DVOk |V8.B<%*+f?jPH`я~:ZMX2FI`if,wY|&4)iRּ&6mr&8)q<':өu|'<)y Q`TYl5;l#i<#Ӟ]s%ICm*JKIj[[64B0 _gq@T1~⣔~ltL[ڽ@4b:@AL >}}j0+o{ ]PEf@Gm$ *(g[/-0恟I,*3@>7|$Ffu$#q14mc èGedH, c2IEs lY ƀ]8X/4q7ĬUܥtHnیC dݡ>!i=$ֻte$v {Z̓L[Jvg3?_ɝwI_ VpٟBVȅtUW0J?- O b _C`~` ~_Be EQ  &! ZA1!`NQfn!v~!!b !!Z!ơ!!!!  !!""."#b#>"$ơ"F"%>$V"&%f"'b&v(a()a)*a*+B+*Ƣ,b+.C-v-.r"-"$K-"< /#3V3>c$F4Zb5"5^""0ۨ"C.821!*>B:ja',tu89;B+R+#,!C0/,1??#C?*<#1!Wa-B<)f#I I$ J!K$0a' 1#M/0$2.!)#)Q6.c'("a'$OO" FB*(5!1"(CU/9f%Q*+EXbGaHd1LbK%( &4&%a!b&N[1>BYN(( h&gzXaS/d>Me^&V!hBgvRBEV<eflaX2c^!#,BT%0&+2g#.s.ct""tNguF'1vJ$iM.bnn&l&z.%iVfid :++!#Bf2Xnyz0 lH-lH*r!#H#lh2rhHx(ቂ(~Z@Hhڨh莪((ꨐhiiҨi).B)2iV) 疾(ި>)fiv~ )zb雦鍎i)i陒fP%'NJ& E&g&)*)*2{aj:"(g((%*gi*fB7q"ìBj:Bj^BP΂-u:g22f&f&5죶VT eh¦hf{&jRgkoVfMa!&*4B^Gt&jbd*®$nAq&2,jN(|e+(eƦ]k'2L/Tl+jl%X~hZ[>eZ!2 1x+g +|o>h,%t6K*m^`:_BLJ-LZY@0Dz0's!'2?3H34S1&'YAq55U73UK 3;{Li!J[;׳=3>>3??3@4 ,L LAG,R8t[(8DwB?BWhdEk4itD _PZBGHs4K7J4Je@lB0O4ktNtAn`4L'OtP35Lo)ׅ -ه=yH }ڱڑۅܑᩎӼ>[syS3e~yUbꯓ?O?W_?go?w/ }?[S/_?V79 qDԿoY1@X8`A&TaC!F8bE/r-[A9dI'QTeK/aƔ9fM7q ԡQ!x9:hQG&UiS&u6UCfպkW_;lYgѦUm[oƕ;n]wջo_ ZuF롎:i_so&nx 19sWq_r1ZDz4g/ Ͳr|%O˓2qUwiy_=z}jFns{e s[뽃>zO^^FN~bC~ɮw^e>^C~}>R5m{@3֎-%V`! fn`[>@%#4 QB-t aC*5 mh=D!:$D%"шKtH&FUE-Jq]E1v1c4cxF5J!BLx7Ƒ@c cԑ%>$!hC񏋬c#GHF-nH xC9.&Mp˰3Tca& JPԏ(`4#>\e+mh}G%u gr5D{LSRte ILcF4儐LhڰL%yKdf7/Xc8|&.is 0۹wP\M=ox|n"DF3q#8QgSU†ШFQ†PF9ANRdt1Va‡ u(Do(u"$D4! [GZ`2h D*:TȲ~T*SqTⳇUUծ&ἄ.pVc,ՆS*SohKuj]kZC]rt]C~jQe ]ܰxE,^mXu2$Kٹ5è0YnlOx ^!J c e2r1ZJⰴMYWW:"j eq¨F4hi+Pؿ~"YyH^rvFą[/^";yK_׆o^BvlKT27o <w6¸0_v~pk RԐDoPapoFQ;~b2D}'D jek0U8p YnXލ8a"cG)^޷-\b2cgNs Z|ʰ2:3!g00c eC#چN)<@NT1]źc3XJsXLx=Cd%oէ. f@ю+*ja&l]0yvrf[ζe{va}Zsض}CwÛvJ}3p{[~mpz;ֳV?w. nÁ+θ}\t.2|_y EOnVl06?]K3n@G ϗ{=g:Uk\ϽgV֟_Xmn>k~p/|^ȴ;&:] a3x)O}D&:=K ?x:w-o f ;_h &}| Kzw(Noa cEzsU+TuOdޫv1y|V'2yu7<<7fx=4M l eL_1/~LOo0 */ "*ДJ̮AİȏnkN*Zlk5ʳxpXmaO- +ᘪ]QHѽ>縨y娍Q11Qױۑ{ю둉QQQ2ǸQ !rޱ2#'r+f(#5r#9#=2.JYtlAl!>r%W Ȃ^~$RR&Yr'[n6k'8El&'y2)f*f'(UFC nj*IÁr`|$_ަG4(+2w|Gwb-O'wn'.۲SR]2-#)'r{*r/ sEe=ogSlNA2S+/d:)C2=33As4I4M4Q35Us5Y5]5a36es4}e[Fr 'F20g7TX5`C6h6^e t7| o$DA( FAT!F, >Je:)E1h(at;u7LD:;;C<&d?>MP@ <"FCDa;-` T 1R]Z402FE3+dTFF1,pt+!8CC E DDCSdEZE#>4DWVTEVID,80rv,´v`L۲Lr,T.{T^DH]IIt JtJtJʓ>#OGIH %Qy.r +RߴRdBmC 0uQ9ԄMC$T@ID@EaJt0 c0-VOPP, Q8ERCR DK)D ?JiYQ 1lR*Am[TJeTTTUTUH8SV|Vc$sSt T?[_k)У_ ` `6avazltG!6b%vb%(b16ckb5c=6c9cEvd7dMdyeUvee]vdCeevbcvfmց dovg#fygh6ehheh}vigie6jI"v#BgkQ6ldɶlamwlcٶm5mcvnq"ȓ(k#j!'n+p]owG puqOphjl#7nBAѥovr;q5\:c9sVtttAuKWu!H[j$t?v;v5w'Ww#wwxWx֒]@MIy TGzzp7zbzzy]z]'w{{W W}'{wy4~×H~}ϗ}֗|w|rė-#8q!{ .XW8Xx=؁)I؀O{5ԅExKVxAXk~}qsoXAw}x5?9xSql؈18k82u iV!x W֍6gj3V׌XØ׏1Sc֐j8"ym#l'9 9/h3`6y`:79g=Ȕ+kWj[9Q9X 9G_isyiw9i{ihiUrGyhYhhglm9׶qy9ffi99E \W a?|:|8o#7vcVLA^"v Գ ";Bd>:+5}c" 9'fQy"W@DT ,d*&u]]s5A NFOH@L!PwS%B bMDQDE©::!UX Q=ZARhEbf,zEjc Ur]W]a{ ;{!gYu*41'\҃6s1'!bRbA=1=-x63XA:M,,=])(=d _֛q}םB׷" !,'''''''''''(* , - , - /355555555420.-,-048 =$C'E'F'F'F'F(F(F(F(F(F(F(F'F'F'I'K(L'M(M(M)L+J!,I'-G+-F+-E*,B++A-->-.8,/3,01,1/,1.-2//5/5:0=C1CJ1EL1HO1IP2IP3GM8GL?HKFJHKLEQNCULEZMF]LF`KEbIEdFEf>>d<f<=e<;;@C><~?{BxBv?s>oz:lv8is8eo`ѧTQǞNس}|RlkkT/iJ=>1ޱV%_L>Woox(h`zi\ם{JTtݗY 6ڃ)Wt)ygJ},"Pk"]GZ]=hWIZBxbj^4&8\½hXf  RucoMigXXtrUx|F_RlA(gjhRkbXb}Vj饘f^SԧWڄJ\UJ4jC:j*무jkڤW"*Uw&~ԙ}^+뱰^v* ফ`+ֻek/ul' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"0?=QQ?PfFqUaH!dQsȁdцHKHC ČlH:x̣Q&b9>fW=F$d)0 |IY@ r5P TBdzDIT ,2ͅH DISĒ(dPr HY&Ay* Cb/N #` 8p@| "q)7 q`syГ?Rʧ-~ڳX'nbɓ$I H)NML:);ȡ?9tT2qfR6 rN<7ȔOve3ƙfPSv%N@#Pҕ =Kc*O;d>o󡏺0WH=łT gM*B S`"#p2jN}̀޲+) `;X|uU@, Okd ׁȕɀ4! >4@j_)(l.ƵPk` Y":/U ]⇻laCq\Qs\jM)A"]n[[婯lH4-nIx_zݭ.uKb#AA_4+A ⇌u NuW6LPk9&ń6SaT\ ډز_%摅!}Y o6H@XG@@-EiQ 2@yw`j?؟Xұ5lSiEP)ya; P(!)IJ D0cQHbH*Vó j-Lr;%*.\GbIhEJi+ԡ4-q C!N_NfopK|=Nl&Év5Hko{≿b\a>̈tP-nsyη~~yN?O;W#N[87G\CGW*w9g.rmS>zÁ.#FO #E{V9ֳA2QdY:_nG9nD+ZA(xB [}/` SO<3$`V A.-\@ P ȅE ^Htp< hAz|5Ί4 A_ |?=|CԷ} px7 wl0 v:f|v3|p-'0$pGpX}pπpz Gp3X7C6'r)w2HHXhuGc z]b RKgsi(wkxr&؆ >Xpr  u1XpGPpI'spyx}N(p}F(~(mXs/ X8p~hg X p7 \k@Ȋ8q؋ዹh/،(pHXʘ茐؍x⨍ؘ渋帎XȎxҘ؎h؏ȏ(xGIWw؂)U~WX y긐y׃~  a4勲ؓ5I ɑ"y#)1Ip~x~g!ٕ 9 {khigKȖ'؄g/@Yp{HpѰ(p=~wDX~zI#+hq*9py x{: H {$"(H8q}|'p= w8pYgW'pq zٛy{z/}W Xp> lj}9V^9vewv߹q|v P `8pi/"~/P7q W'7pz' 9xop J{ hǡW'GtPIʢ-s)'J#19ʣ g飦C'?jht礉ɤnGME*ܩ)V\ja\GijJ< E At@  &dnQFz@tJ MT J mZ@kGJ@Z`J@VDEک:(idڪ$:Lz:bګ:Jz*JҊ:ؚך M i:Z:蚮ZĮʺʬ1mjH4*j[z OKP^t[HKi\jL˴ 밭HWpnf "!ڲ.0:476z^P ` P$PKU;˳FʰviTˬ]KS*+dkb_5K,OP_2N˱1H?`jn\K KT<9˸7U(XkIiUr Kt jYY l}Wpmji_&^$Ee@※;ȥ\ѻqËʛ+;[K˫ѻk k[k䫾Sq[{ ΋+K+㫿_%`K [  ٵ]+,`),SqbjX$[Q{_L <|5k. ,Ĩ˺ɠD: W\Y[]_<5[o$$[|R5 lpne4umA/ ,#Kȅlȫ:. ! ff$pQkorPLZ[t?J`ܳʭ\쭱,˳d ?^Lk$i Pk ;ժ|<+ "+KnjJ LVs\ǭͮ朷*Jj l̮m} 8WD!h m?Yp`?YM?J? o1b$ªqe*]Xfo>6y"-ҩQӂ8])VW@KDڦPR=T]V}X8F []H~d ]- a c f2*mɀ I"VhtIJ PLU8`P naQTw Jya|\þD=؆SZؿ$ k- ؂M]IDY% EiXِِڈ]!ڞ6MgT` * uI6 `E$0m"gMFFdAcda݇ %5WYNd}Aa^qJ-de~dlU~PNYP5 .!^czjmT8m NdN Nvo`T]W0E10%O~TNTb1Ux\TJeARP^[_q[Y2Fa^UP4w~Z%j{eXfRㄡB+Vfy>Wu5W|M Enژk~Y"]X^ZꗞZaꋅX=갮`$DSne?%<ꥹչmN$"uU!Vo5. fW,[;NËaP `O9$ueh$%_+oL^N$[>_Z?ʎK"jQU5R5o?9`GZqLÑ$ i~\n >`M5%^cF]V/%\LY/dT?KPvpAnNaPOīg>.1aoVѦ&"CVddJd1g 6xJmd0p&cسdT/ɩhf/㤟ٳff oR/TF\~gn/gROba&$H OetVA JD"o TH @LNDHζmv6O-^ĘQƃ~JP/S&HРŅ F6L5-!G&MFZXMlص'϶{.nݽ&p3"6\r͝?]tխ_Ǟ]vݽ^x͟G^zݿ_|ǟ_~0@$@{dA0B "~x 7C?q !+OD1EWdQ fqFjđsѰ{̱E!D0b0qa2q2PI'\'ՋJ*|2K&L)/31,K43N|D93O=4Is43PA%D! eQG4RI'RK 'SM7RO?5T9%uSQOE5UU+U_5VYkYo5W]XݏXY^;E{|y$PGȀ :m1n˭m}\]UwxusM7\|7!z}n^bw_eVƹk6昷۹~˙_:ecE馯hK_2_hȍ܈;8.qVꟗ6:YSKBA^,dݖmqmߡG|ƏpsƜ:ű̧ڗHGlq" 46l;q|jev5q{♎W^t . y_za[{ϣ*|f濏q[zGTuס9(im\>f+FD-i aA65LCA7iԒu@x&pJ"` ݄V:!&R>L /ӾtjIYXg@n0p@Ђ' C(2N\ߞw=-qJsDkV.1nԬ'8L{9T - *⇲ {Q1`EXdJFlEf]<_kI9Gɡe,^#ͧTє,9i?VDtB|f/r!YhMkA%1<1s4Nʅs9$\s`Qjdo1t¦bQ-lq RFrz:ʧ(|2YiHE:RԤ'EiJURԥ/iLe:SԦ7P恐i ?jPDI jRaUDdjT:UtZŀ*~bU\WcVgQQduk׮Ck^ZGկlnjְENrld%;YVֲlf5YvֳH_0ZҖ0mjUZֵֶmlWZֶŭmi[ַo;\E.p\ָͅr]w.pk떖EvozK[ўW}{_^Wx WU&01Q+F悴>ȅ'0 6ָLkD}4@ZQ `9GSc&cr|VTp\Z*K:-5:yY14k dDz^/K[4ͬŰ9|:Aht\Z-4X Zs{0Z*ֹhhsQBˤ@`xxc CSN1,pMZT NmG;&'[٧5/kMkiؾ-lbߎ/i X-mZmw=Z4"׷6սmvw;~,r]@#>@/ PC[V{ŸUݘí;ځ /{ko9WMd`aXHF盆\hNS78|Ϡ 2M/MRZ8ətfx[\m۫wN O}}S ~ 8M ēV;]R }E?z!c1!lP~C+G/半ɿяEw~} Oy7Y4jZV#礭ȾhS-3+* /ѫ.k@P۽h3@:+9S>k3@[mH5XX5A\A<A! C>C @ZChs>;> |!A4+,BB,$B\jŃԪ-#B/d.$/DB⯈323d? ­{:788ܳ3-=|BCBҒBI-2L`B4pJtCJ4T01*KP5VHPDU: HXXҪG+-60 \wl H˳G@|~#-\;Ғ[HȇL:8,-tH|KHbȾIl= íG tcxutDZT7l?k<:Rǝ\B'sJ>21v?-?8H@VJ{ĺ*˳LK⣰lJK]K$K|>i5ӵJTC´KD ܷJ㽹?TΔL;+MI-L1\-d4֫P07OP>VRm3FGÄ3l> {<`ΗNNZsNLNbmṆNN$IN s,l |h@L5= E[P Mڭ|N$-4-ۤ r-P݊;Qm-b/QQQ!R׊#%R%-.&-XR'$٪Rߢ+53-}R.R/,S1uR(R0 S4Q5Q6U.7R8E19ER:S;=:S2?S@RAMBU:C%S.MT% FuGHH*bʪINTTO%RMM+SeV}TPu,ZU]W,XUp"2cu0"ZVP#ilj UmomysEtUuevuwxyz-U 9'_}~؀X^؂5X׃U؅u؇m XM؉X~؋X\؍_UxؑՎ%ٓ"Uن]ٖE YًٙX }ؚY M5 i!ٓ٣]ؤUڃeڦ-اZکت~YH`9;8ڬך0,(0+Wط]ۿ-Y%\[=ܝM\[5-蚯1 1\$RZ]\z[(MYUXe]}݁u؝׭]$`!Y8 x\ Z0[ U]]]\M\=\p^pi!-\[%"^#*hZڟ倗V-Q '_qH2 V``&3rff` >`*Av G`n`ð ^an~n5_N`a.a vna 6bfb^n`pa!Vb$` b b+ȊvV/.c/& L(_ -#J"uc(5cB>d!b"~"+~_Fvb)(dЇKL%e^ؕ^ -eQ>eMeU^e}]ݰ!$"^MVUMLZH-Ӎ^_\eXfnfgWhfiF\j'Vp_2è(&]];xΕu.WiA]\y[z[{[|Z}t&ڏx\èH > Xp[uivnhhPh~ZnYf}feN݌fdvV飝-idiiii~閮蠎ىid.6j}jv꩖iVj{d밮pIfk k}$d0 dֶ&6k{@6^MFvl0 }ɦl f &6FVfv׆ؖ~׎*ͦmzE0@ۊ ) R@nR8Ұq'4[(]Ȍ_Y nlsblaPD*&~)o Or%찅%XS]8]Xo ebo@$C:yOc00cqb(y%G gqp(VqV5AZǟpq)@& .y ?$ GhidxgNrv5[QHQm?v_B"7 #H.莍*~ad߲ s AO CW P[¾sy%G $ׁW_q9GdGS@+:\ڣɱu cB*TWugu5_qcB&ױI#c8d~>UК@VGC*hP&6 shOW 8h./?#0z,"s L ، GȘr7WgwW Yl7+zyg*E1?+BuRzBpzRq'7GWg{ d?{ :&G R8cp?SL flh0ke(?OP|Gjƿy@7׌H}8Ġ}ן؟dd8PS?O|ocfɇd|e~|G?j0g~@8o}+}^RMz8|R{S,8Af 6SX!Ĉ'Rh"ƌ7r#Ȑ"G,i$ʔ*()L21$x*&\1Bwшǎ\ڔ#R**֬Zr{E%S'd=7{0Ź 2"=ݓ-(~_s-fd/~֙x%q^=YpҥnXY=pO$\qr*( '3R~& \CੈFVlZmE*:k`fJ+++ ;,{,*,:,J;-Z{-j-z-j;=S骻. {/:W@iū/ <0$[[0 ;\0[|1:` {1!o|2)L%21<35|393=3A =4E}U/(4K?4QK=5U[}5R?5]{u[=6eg-i6kp=7m}v㽷z]7sMWC͸㑿S^y␳ ,9}sot >$SώN.:t^{޸K~n?F(2tB|(H54Tx2(T,]<03 QG=<33OJ-LC.<Ҋ7O lT5 1P1@=)w ,8Jk`08Qa -EFсJ[ ]ػ#|T(EF}'La`*M|d}Pih(EZ(ciܡP"u.qvm881jba ̄7( { y%*K8>1PK YDfK2\Z_'FauHFgGlIKqz Lark>`tJE,4LaJ5g>: i2 Jc5Ag`zOc|PGridZ5YU |1Zt3L[:xc ](Cpu<DvтXuG Q.p")FMN4!]iFґ~)JoR>iL_Sj8IS>MI*RazԢ25*VZӞfu[VOU^}jXaZ҄.uk[иU<YjV敬za f=KYk*6{*T洬4fG dRJz+_S Yzj{H(r44l,>9Bj3*-}H].7ͭs7N[L‡]LQTZ4dW##Ǧsi5gU_ ;䁝q_p b g&7't!</ǔ-$QF G|x? Fwp.4 RE"H?zQl*w.A4}ju`Z@ kt2A)c|0 *Oc/JLIҶ'fUz2ɞ<Y|k._4~qhKXYiy pmd#Fd NҕiMokwmKL@U3{a8Yֳq4TZ׺4WW6de5=cf+{Ѯ]mii6-q8*bsAG7'7.ě@F=53)= s(8ƯbgxGP( ?9S^+D|BP9c.Ӽ69s>9Ѓ2:ғ3NSVԯs}Y:fӮ}nW{.w}vF~w|/x}'z+~o<yK~kH1Ɓz|?/Ћ/=Qx/_?vE9Ş\[}ħOxמwۓ}k$D駞i{7?{}ӷ6{o?I]A L*( Me8@8dRNBU ICTLf@f` #`  ` ֠ ` a  >aFB FaR^aV!fq   !j" !!ޡ`f& ^aa Fba!`jB`#jb#n"$'f(a)b%"*.`$CE\2]]r0_0 1_2b2"_33&ȍl_46]1B6:_727*`68]92]8Fl^9>_??b&dFdN&eVe^&fK,4-dfmfgz&[H2eA@-Ȅ,Bj'`,@DA5\@DTǀ(Ȅ)@Dlfmfnfo&AHqlt6y`,Xqn'tC&x^jkR&nfDu6,4<&A)#$->/AmBHAA44'$+B<hAh.C4(A<(AA\gsV/&:(>hV gF>FĎ#腔 q>,hgD)DT(At~ :VLxC`Ch)zC(/(E؂hD##BriA4d_=NjC@yLniBVh@@,@#DꆦfJjCtrKr(-̥r)(@^k+kk,+i|^JfDt,DA|,n,Ȗ,6jĠ>ɾ)Al*kÈ,GmA~>€'A)+#<Ȃ|i,BLl',Z-j-F|ퟆ2nDۂ--^>B)Dvm-A@ g 1D=@CmB/RLp8iZ:L'$qb>rnAxnnABļGvEn bbVn#Xp} hTfn/v~///G#b^[ű6//2 jeJ]/K?aS?pQSF%W_0go0w0D)  zY5Rt35UrL;GN/TmAnBm.nA+'54'TkYf…4R˰tSNJN521_F)` F.n)6@&6^vhv_{ h i6jj6kk6lǶ@Vl6% n6$o7va 78?Wr4-17t?3q+ p[_L7uSv'tww@s7xwunuyh{ m7}׷}7~~77# 8'/87#x?O8WGgoc888x888øxkDӸ︄880yy#+9;3xK+9g9o9F28Ճ2xfB('?C<Àw5L×'x9y--$8_x,6XC0&yk:zCGD*lz_:{zz? xkAĀc0hzpzy:H:w-\O*:q:  B t AӁ;'{9ς0$8K; :ko$C1x1y G8-{BkxxSCDü7g~>~G0|Bf~L W~>7cç9fGZuL@adJjT *ƠC($Ħa<{m۶jtTX*mrmaҥMI,Z Ch׸>k%Xu<.\+.L[V2eA5k~s";u8U- t%\8P!Bchx񇂍'7ݐFט`M6L O* 60 aZB%;8lY.O&ۯ? o S"K,h#(T0@|G40O͋iNc #vvd8dm CfmEf'u0-MhVhjMjl]tjŮlN;0~-[&l;Q[1_j 0V=>K J0y=aX%q{bw(DAS/| _|PPk CY]7 >u@tTBl2@w+oE&DPsd`h߫b4wUs gYȥs $Iil '.p T7nP;l99rr8K@d ">4PgTA5UmXzMuŋ5 h'$,A 0iI3q@olъ.5@OM_Ǘ6O qyn7s}xz޺7 X@@?S^v h4Ҩ4Zۂ(@MGL˪զo_Qׯ4be8hBטg3a>;-@3\}9odL+g~f>^k}?Ϲӓs=\zr=Yu=vUn>w=>^r7't;Ot]|=y?޸N0-o_Ʋ O'r@xGyM_o41Og^wɷd^w%2Zk܏U ~<䗳qdAsPpg4vtQmoۧq( 0ϢP P4ŮpŲŶ! x0A L ,ɔɜ hJ |˪p: 11PQ" ˠ01  j LGsM 0]QX L;lic U K]†1qQp곒1 ,*1qܑ1qr ̖ r!!kl!%r")2Ǭ*r#9#4NG1;.LSd LhhXKg"2a2e!.T[ 5 Ldu& ~C` ֵNWYLt0P8' 0v&&7u dVaagB!0@6m"fv&8OTA֠ 0RkC i hq62fmqV& g1hk3kskikACU^K8w#-bt cL>>P]kw֧qWO=ss|JMS !J~&^eb;*:Vvtg{&Ж&bkhsDALQu D`iBw:`f1tvk Xbqw˖w[Cvt&ܖ]nIyxIaR+u&W|YU=p>6q?{{1[eBug"&c'MP4TI;X=8ExIMQ8UxY]MѳixO!PakxW&nN}CՌ84UJXM!384զx8xɸ88a8x鸎x88y  9 ّ!9x%!-95 ꘓ=yXIؔQyXY_ayeyOmy]rq/z+9y97Y@F 8 x98 B8 {9`jrA|Z؝9٠͸!:%z)-1:5z9=A:EzIMQ:UzY]a:ezimq:uzys}:z:@:yZa ! ` :C AzA z3* z * !,121211111111111111111111111111111111111111111111111111111111.0''''''&&%$$#"$''''''(()(( ) ) * , / 3 46666555420../3#='E'E(F(F(F(F(F'E'F'F'G&H'K'K'L'L)M)M)L'K9:b;;c;d-@d)Bd'Ee&Gf$Kf&Ge%Dd#?c=b#;_&9Z*7U+6R,5Q,5P,5P,6P-6N-6J,5F+2?/18205414;:3BB3DG4EK4EM8FO;HR:MW9S];Wc}BDGKMOPQQQQVbo[yC~>;:=;<===?BLcyĴljZC?????????H*\ȰÇ#JHŋ3jȱǃ>Iɓ(S\ɲ˗0cʜI͕r r ;?lsOGo*]ʴӧPJJj̡<ԡVAvMsW?f[0շpʝKݻx/%kF } K0Y.Sk١^H8˘3k̹R *JVgVQMu띍z5מ Nq}FN,`ȱcsr͍0gjӼu|smc[7Zl_Ͼ3??bwNLjdY}gdʙVڕw}-_UhfM]1]l(t]%"XIAvk s"Jsyh~TV ܆@)DQ>Vv}e[}]Y2HhX6#f5F)N݇ l&fF|u %'w99wbyA%*ww曌6裐rH[1ԟ ܝ_ܠxF*y\IjP-*H)x}:Xj.k&C.7CʷbZw:RĂܱ+lG iBTrVk2&+aRBE 7-<Wl g ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZXդ.zH2hL5p(:k#=걏 dIBL$!F򑑎dFHJn$&7yFMrb$(G)Q򓢔&qK|%,%)Y.!Eqy`,k)D<&2e=hLF'D#d/a.B'(3KEj)O1^:uψ20QD$#L@E1!"RF/Nr#HC*Ǒ&=i&N`ה$ v1+ u\E h}.PLwJFt=OSSd%^j1&c\\7U)LU x nBP貣XmdJƽU_;0Nc#RH+?_<-jNzuwKZ~w}F#\\oxyAa6RpGGJ.  v!<8p1, W0<,asx!La~q3 x e|c%e\AcLc!9\\2 !Jfe-(q2fwg)("ۏ>V}Կ֏}aؐ~'}~'4~ǀ((C~< `} h Hx((a $)3p|ŁA߀<@:؃>;؃?HAhD(GXƑ*J@jEʤ ZҨQI :6YNj[`]_j\a:dʤAi*k mZ6j~PxڅC|:HR ~J| :ڧJJiŅ*g`Jꥒ:\J:کڨګzz*zzL{ՊJ*ꬼ*:ZJڭʪ30ڮ:zjzگʚy::Z 6ڣsJ5YJj˱)( z2+";/ 1 -ۚ3+79+!yY;CkE;G I۟K˳5KOkQۆ!SWY[˵]_a+c{[{'Zf[Mm˞oqjc;Yix }۝Ku;a;ىZ;;u!Jg[; :-  3y!0MD L0N ǘ RuHJ{ȨwQ@ x{+PAKK @ѼKvk[ːNc{K˼K2: : ,< ;pк Ԡ ,ҠK討x i`x(ܖ:<(1 _iؑ>B3\GLJLAP5|8?\C S`Lxf,s 2H ?\q +0̗uqr!"<}H)LaYE\5ǎX{I3~ӻ NیD,Mܢn|k n.vL )is1$ˍ y°N I Āt4U~9O;_ ɌXnl)~xcN20M8NKG;s![~븞뺾n34)4Ix s |ِ>4=-x* =nT (3[8>.43׎xcn ~2ȈǧX33}HG"?,.02D387Y ;, ͸(6"YN8'sԖ. 2! j3zuk! (,2`W_OJ:* ~_D 3mN>G|> wmًKٔG?X3M\_) a+3 /.)} }9*s3|μFSȿ9+o_o!/?O: .~!ǎ(oǾ}1Q*8{/_~1C@ DPB $x" 'PF=~RdC-T䌑-]ZfD^ę!˙U4)O:,H4bϞE1zTΥM 4iԩ] 4%װ^uZ5ЭmŚŹVnVbśWoG ﲵuT1A8R():uWȟk<9deofZȟ9j;Q{Tlkگm߆]zcYгqM<Ŀ\r͕?8__=ֱώ*Ӯu{J٫=]ocg%o?f08_;npB-C4 B#CQq+cF O@1E@## qL#I"P'_s*t.#â/-=ǺlKްH G&ydJF9eWfe_Ycfoyfwu9hzhifhViZj&YL:앹kFdE6;mX[F;;nۣPJ%yyfjFD$I&I$eX~Ɩeǖb͓)\{ɧ?}úuzNʹG,|\ bd4/t;`)X o=XuMhY<ٔX"r9K)`ㅙ&+}fs(sS:-,!Y LXa,"[sEfL󝥐g@Q&hB~QTb,G*8tY^5G/uPlh:e%0]^9L5Z${9h<鞊 ƭ׽y\#/p!2\̙`ĵ fomjՌBn PB؄TnpKA&wx0tʾ mY؎n1Sԧf cwmq۾qɑycREu9<Mrr96^?Z7NWu8,n.NnT0ЋNKҹt1>=it:wBs 8Su]dSbc>|ݞaaѺ ꝲK%w3㗻G8tZv7lm<>l/}XԻln}b_n{Y/Lſ ==E/}S_<9/>gs?e_x?K&~ӿ $@f)wHP 4`Ya ܊S@$4P! ha 4AlXAx!$B#yٖP"l B(*BhY~BB$)3D4q&5)D :;RaCyȆ?CDD%@CAwIJTKL'DONQ$RPDESTUdV TEWYZXD[]EVEK`FL|H`ZEJL@0my :iFggpE(%xJP{x`_W0JdS&ȆSj}?fXjFXf؄H8cL?PG?(W`3GI 29~ȏ E 3fT[肂[ȉ|1HwHHlAž:#엠J}!Jpqa}pTdM8FLt](_[IțI)ј.ܑ"õ\·KdEl~iG\(؄SaI]0L?V肟4HRʴLʂJr<<\ȗ,șB$hP˔i@ȂƂ؄M44.H|<Ɍ@DؤNFǂ8(j DV蝙\pG4dܘbdY\OXPIDU@uP = EJ mP%QEQ=eQ(О\QQp -!%5#P`@yR%$R +,R .P0m/S-%35Ўp@PP@5-R:%4S

Q?S@RATB}P& 9RC=eTxT/ uIuKeJTMTNSDkL=TS5ST]POeTV5TW%TXTYTZ=SP&9e%o8V"bedV Vf5V ёcuduVk-fQbŊhiUilVtuk]tWw ׹,q=W{vVzoWzV}r C_׀օWuWueWh5X/|-Xs}ׁu؈~g q؍gEYؕe؇}Y-ِX׏Y~ٖYXׄ؞MY Z٥UWhӒEUՠU>M\]\m\jy \m\tqɝQ\-U%ǽ[%#-]!K RQQQQR̵\]QߵQQQe]5^$Mm]]^^4]EQ_#0E+\ LOtDsLDžjN@GjfHg8HЁM-x_ \LsR`(|%(DĄ<,,4_pG0H$BfpaTM4H|W@_# a@I&Ζ|bIF(F(F "tbX0M)>!vP U#F5J61):>+N(G]lJЁǑ҄\G^@w 6d%LQœPeb`NNNP`˒KX& eĄȅWpLPI6f`pbѤ`(`ecɐfxf2N<>c6Lpb,LFցjxXA,&NiGxuw]@w) z>&dT$-OH@Ln\Nff`隶&6VNEly0KUjxDS4bꭾCDaD`QTci겾qA@yCE06F% 7DaVC,gkIٶ!IR#Ŀf:$ kS N P(PXl4~>K<Ŏ61$ 8vmmז٦ڶ>Cmlk<5IFp4N__xhVEdj`h7~_s`.p5 P`Hg,J F=fFHE&Hh>,̅IaDX? p0sIK6Lc2.Npfpt>M h0h]gg4f;r& wp8wx jhrOz_΅ kuP'fNV7^w8nxcx6iwx蒏iԲ@xWjszyXsWF:__֖zЕ0,jduakyDN??pӭ֓FTkW쪶ADCIYk`{ m B:=llmg |̗WO T{/jK/Ag>xO|jїo T|/}ЧG9\?wZq f|d$jG{f'|/Gz7Wgw> &,R,h „ 2l 'RÌ7r8ǐ"G>I$J&SlY%̘ Wʬ钦AigA<}X(NJJ%*ڼyEjaիZ!nj+XY^KDf0Nދ/` {VI \t"w ]Ĉbgl-ä0Q5XJ _Ā,eҦCGaɻa ޲'Q{6g<#iV[{11fNZJY^mb+:dd8Ak4|@2YLtmeJHs.ߴV.Ը ]yԧK;HR@ByN9?.Aq /%6!MTS Gm?ۭ? `IEωF'g#}-t, -!Z֢5Fd4%tb0AƖghp $cLx`QY'ډta&!(+BXrD$RC(")~b8A&I(IX1KXF5&3qx9Α8"ۘǂhq{#E2|$$#)IR$&3Mr$(C)QBqU|%,c)YB%.s]R6lɔT19bJ|&4)iR3V<&3ab3&8iyMUƛ`*1 NY8g-9x6Tg8}򳟏&:)Oxޓ'B IԠԦC2M@YςT(HC*·|(D9RM6JpB٨i3ьjT u)P*ԡ~-)J{ԟ2N}*T*էLhR#Qp"N掱Bd-k(:VUfukU*׹g}]Zex+.$dZA׏uc׿5 FOӬt,hCMԳJfVEZX^l> l,Dm%򶷾%Cݞ=) ܾVg5K-mNZr޵&vy:{:=$Y^W@.z+Z/~/,>03~0#, S03`##"٘ 4,A"qD.~1cL_Phs<~̃ȠeS$8h/2}O\P2LڔJƘӬ5F2l6ӹv3E1y#s",AІ>4E3ю~4#-ISҖ4rAMo4C-QԦ>u=Uծf_-Yձ5s[׾-Qzv\'{ζLgS{Ӯ6I}ls6Cqt?{Fn S}|{7 /8+ o8K'oĦ!no~ǭre|6Krbtg`kUsN|3ZEGÉfs͇NtONͱr( VD6Xi"|nG7]m{7}ɻD{nw 7|xKOx3<+/zO>'3_ySW/ٷ=M/o~}Q{~?>g0|?履|o|v7??w^c_o?֟vYJI\iZʼn]iq yƁ ĉYՙ (< " \ \ Z[҇[[[aR\[v[r.zavZZbV[rj[[v\v! !")"1b9b :[$~!:1$&f"nu"(\%b)"*3D3+*+ ڐEmYD33lB$Df`NDVy[DR%KNQNeNe%k.<7hd.R&Ef.?RlB+NfZe&覉y?fq%rcd~f\gnB-A/☧%'9x&{~ "!y''wN 禽-Xar Xc( og~vAyy.'n= 'J(nzSIR4(eX`&%of{(lhD(EdE^' &a'Nh6")D(t~5CEYTƤ4&HC%k([[6Ҩ*E0zk iFvD>&'ʨO i)i e)0*iEtAeDPg.Hc8)6\iDdZ$Pl'[0lN+LFOg9Z"%&DsDjb&y)%pj˜D.2.+$Pn+(i+~+++++k*YD؛+)`eÜ++R,F%Ê=%5l5^,B=a$-lǎ, kD]ɮ,rWz,֬,,,--6vc?#aԎ|dhE>TR"N^(ىv]%%Z[-Vn%a.l FBɦlJDm&nVr//k&n"{2C|F| "|gx⧇+8(lz.m&_. `ÿޮUFěb&iD,6&+L*VR<vP-Bj*B3*#zҾ/֯//^/#W'ga)e0i 0gRW0."sp3#0bp5 ;M̂e s0#XW po0 ڋ̲0w08B&q# ?p  Sq1p'cqt1qπWC? qg.."ﱘ?U׮2򈙱32"49@1Eh&'`M&w,.2(? )*2?}2Dz's_.r,# (-+ .KD/w7Y0#rX2/1 ?$+Cs?2'2?5S /#O281=b5W9;s37r6c;S+r41E3zR94#m<@r=gCOpo1?TF4m4G4HH4IIJJK4LǴL4M״MtL4N4OONP5QtP5R'5RR7SSGT4SOUGuU_V'uVoWtW5YX5ZY45xB45\ZA[u\\]uM^\uZ`5aTPMtW-vJ'd)t*E>$CLB^K§ LCdϴ)4=4Lk60pvJ /hôKBdt*P'n[6fôjdlN&aTnoAe Pppqöl'JtKvot_vfo6v6rswKK5RCxCJA3 F5pwJ wQ,̂,4xӁnx,*ȂaTG?NT3xLӷ}uwq/7b*d)4)\t3p||4o*KkGJA//Tc8xM{Bt4wy{'85BJB4̸JJ?D*L98[sy۵9[-SyPky4J99ySßy3zu4/}ͣCk|y=U> "W~9o 3>.h.{3 R sн?!.\h^␠Y`댶bXf㢼OyQ 3Fԯȅ ք'7"C!7{ȍj!.A\⥹ 1lG1زK׼zх ]SC;욡F/R3$;ԓ\H A "MDN;5SF97tP0/0DXF?o/Q+-5P/TuNpXVlY:F:f!-c񢫠cvMk6|s!jtPV b2myr*ĽN1=bi I~=׼" qm~ :^8?L7{N)LZ8|ab x` -\XU{y8bσyT@[d8Y4g&hqM]. (d Ye_YcFvڤўڴ/{njEsL6KۦuoO :*Մ)wlhg={F'UK=eW7wWwM' ԝΏQg^y]wz~Om{%|^|__@4@. t!A N1A n3 A#3H(-t aCΐ ms|D+!KqL)x MtE)N"AXE1HǘF5mtwE0ʱ?&&"pg=qwï [oCrx\#yi~su\oU`([KvӝSG]u'W[/2",;~v!ig{}v;nu<Ȇ<ú}>/|~ă}^o~D$0!co)EOU>7GPn#xD}kӯ~= !|{@U·~e_ރ۷}__so~k?}[w/? LoB//0$4P(/=;Ppc/ۯP6p˯ϏgmN)O[NNj r    wP  K )x0 0MȦ NI uokNea}PPp  1Eqn!216, ELOTSI\g1koW㲭}1q<!qtB#I| \#ʨ~_,ɱϊ#7NHs79[3:h:CǮ3B#B!=׳?9 .ˎ`7] 2<#T?!TdQ7B3~@%4#C=@/CIDMDQ4EUtEYE]Ea4FdfA#"= F R2I|+k>M4~#rjpՊJ42oIhH#*ᦤϖ@#QvAK*l!ʴ(M ~T!?mSΦ5]"U SլTx Bc4 R9#Sa66!%tҪ--*UY5>䪤LP"N{Չ@$x<*E.f%=(%[,&QlhKCtY1ž6anHT%y&eR^>U@!J$*Rq#ZISW~BjR5ʊr5aJT M@S2NbaW@tj#NϔUeghVG%Zd-4\g5IC#J=P6 h1 e6keFkk6lvlɶll6mvm%>R,6ncdD:9ԅ<Vnnt6+R2wq;^7 hqr-r5s|3`rAH7tU707 7>q\Wv jp]o>Ǝvm7x :32w7ywyyy7z~zzw{{{7|w|W{|7}}w}}|7~~w~7W~{x ~ X~G|x!|8)|'1x{/89X 61AEx!xLXKx }aa]!{]x[8xy}783X{'c7{{a t_tA=zɢa0{m&wz`7dX B˘IؐOXYؑ |1!`/a:AX ZX l|̘<{aw{IzOzyxu 9BzX{XؚXY *z=,Y LmD{u!7Ƙ]J8Wws~z8ezz5X7)/:~-:7)ٛ&UW xz~N@vy|!ezUgڅz݀G Y z !H" !Z?zz#Zź:ڬ:ڭZzڮZ {Z:۰W;{ z!%#%۲?[9UXWQ{C;Gi;Mڳq;g۵Y;_;{Y7mwEyWWڕa'۸[kZXhA8[ zںۙa{o['Z J 9߷)Z%YY7{Qz{wݹzU_A}11aAy9ܚ[_\cg\koܑ2)|yu!Wky{AY !zQ vaYax17Y i7:ǯ[ `ax8έɷwGռ7!oM~\W;ӕ|SչWԅXգ[/-Xw׳oW؇'رWٓ!SYס]مڗۭ]}+اwɽܩy}a Wo{xI3}̘19x[waL_ &e@79A 'Гums~ ^6wa7/6B!2/u)v>57rcWG0 ;[0(te~F7J<)eг:%7w=(p[ tw>A 5)O{ r4:ݛ*,߈n[~W^~? ? =b^!L(#!1mc=/qAH!#kaQ7m_s!o??9_%_ɐHN#8wQ_sQ?? <0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z 6رd˚=6ڵlۺ} 7ܹtڽ7޽| 8 >8Ō;~ 9ɔ+[9͜;{ :ѤK>:լ[~ ;ٴk۾;fݼ{ <ĥZk╻̛;LOrf[1їg>_?1#/>nͯhdHgZ;8B~`;H#cP!,''''''''&%$#"""$%'(( ( ) ) * + - . . / 1 23556555555555541.-,,+-.//!0#1 %2 %3 %5%7&9&<'?'B'G'G'H'J'L'L'L'K'I'H'G'F'F'F'F'F(F(E'F(F(F*G'0L*3N,4P+3O,0K-,F-+>*-8-/2-2/,11-22-35-4:.6?/8B3FBDK;GM:GN@LIMOFWNF\MGbIGeEFd;Ed-De+De+De.Ce1Ae4?d8=d:=d;b#>b'=b2;b:;c<f2Kr2T{0Z0]2i2n1r0.,-u.f/V0C1222222222222222222294W;_q;fo8lr7nr6v{>|?~>=@<;?BIECEEDA?@@@@@@@DW{֟չŽƔyzqugsgthuzm}_tIyA|B~EIJPSONQRRSI H*\ȰÇ#JHŋ3jȱǃ >Iɓ(S\ɲ˗0cʜI͕ r yΏ<} ڳѣH*]ʴӧPa% џJV]kBQÊKٳhӪ׆l֯tu;p'؋ LÈ6\RV6lW޾A)rvfͪw(q? լװc˞-UΩ7C6ݙ'ܖ r&׷d ;<1fӡu Teسkνw.V3 n<_(sdy2Nߡ(hJt֑`i^ ęz_]U`9fgUWx,8~%ӱǟ}'WP4(czjAWhFdxa#j9TVibѵBQ}#؟c`"UguޛJx&ݶu|X1Vsɍ֣P7GBN2Hh (f馜x{6p &(R[*무A'רQjWk8lO&+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z u&$ W0 gB8̡qh@aH"qFL&:H|)ZUfq`HFh\h|#(9v"D %|2 sx{H'2}$)I#RGd/r39 J:2|eXҋ-s E^NqtcStc0ede4hVә\f6W 0%!iNӒ:vsLa:IOzƓg;L}&/KҠf8pzT! aZ(E/QZ'(A0*ҁԄ%I1R&hKSJKg S=LcӘԦF IUS7}jRZ£էV*N΢bU?*X*կBuD'WZVUgW:ֺFUJ7׏k`׬uH5+ZrԱ}%KQ"Bau+hO*Z2tlb;WB~`Q-frbӄ E,M~fҸ@$n+ Jvҹ E.T6^8F)IVһ/$H*Ҽ|DoLJ1|2CѾo}Cѿl&.Pq$p\FR0%J Wj41|`o.t0,Ab,Q,*aG wysGz=M!s1xKAH x8wJኩGaJ!w1G PzLxrȋ>RqAa*P6 5(jG EwhoGG su GxH}׎(vHj'gphh~HVp Xj9 )v)}F'֑=Gi|i#)~&ɑh.&:0u ow49Pv0ePd( gfkyp הwqkfsF(fUhY9[]9th aPjnYxQ9dNVƖ_ yVx|qwsYyuqLєn r)xf! h V9Ny0 }Y0_e[eI&9 e9YƉW0] QH^p0bCTI{HP2fw$nXv9좝ō߹2E9iHDiY9/Bp'ՠQᠮ(@Wٞ 2HhNiCA /t^2%9y.5N)18ש7z>B:DZFzH"ӛ yJ1֌fmΆ/Q1zG77s,u I%a*vxz& {ksAg/ែ!kAjaVT~vTFI/pJX%z*Y/NuTh섈&[xmxo*s1y8s8wq9S(uCq *sk yI\*ä:ZzAJ:* 60H  H=.a:ֹ{;湱E +#;3nIರf6Ᵹ¢ۂ4/ P!=CB /{AjQ P[C+Z7kXۡY{C[.>YJ[cQBNJ [fkgk- Qkxs)dK[[u<[+a;.rKqK%Td Nˢ*+$ڹ 6D?1˺2ѵ@;_۲K˵ة x+[.Z9˶`JJ;ZI{EYJ1*I+۪b+{9"oT˴˷Ka#{+{4[+1SK<4ۻ-|+b쯥s{/qc˿U!Z+ |'/&0<2̿K2xHK뢋%Lĵ[b <^xbzˣOT<]3\{.j cć!džADžaDŽDZ ~|V0Ȅ\ȄL ȊȌȎȐɋȒ\ɖ|ɘlɔɜɞ,ɛɢ<ʢʤ|ʨ ʩʬȦʰʯ˴<ʳ\˸ ɷ\87 ̌˄|̆L̃ll,Lͨ,ϼΰ `p΃LɸN0hs@X hF XȒg`Z@lmP<ϨP s σlZ@Њ, Lglh #]҉Lm) i+҇칾繌rXL\MV` 1<V`d @ܠZPq / Oڝ^_]ڀN/_ 0(?)>}  _ș,o%O@M_7?v`:/+/-C=템b!OU?of?>. w^>Moȓ`T_D_h/g /NpzX|/|?Ȓd34rșOwo΃}pnpb``z3^ϻO-B<5oLlܮ콏? On^oaŊz DPB rQD->Ō{$G8HJJ-I;uqBH MVn1x ,h%#I GR ViOEY l5aҥMQH^TqZ'PjTϦ]VBhhaWw"_+j_>sV+jpJ+ؼ5r`̗q 5Hnmf)<-Yd֭*hw䦹rV4s!U%i9GW;;0o  X/9YB,аYJOA{o? 6zQ]Ԉ, O?{;c (( Q"Ae=>\2q U\QCX!Ph FH"ǮvİGp*!D?&9IpJOF˃,QK/\M~ I>մqs!3H9hA>4!B =D,Q'0RIW46K/0Ӝ69OCKG5? 4FQuUXuVUmmV\1uz-`rՖZ%6a NnYhߜjַl5%du \^ =]U7vbn祷^{7_}_8`&`F8afa8b'b/8c7c?9 YdOT:wQyegfo9gwdK&|Nh9c>Z&:jjh^ i>:l^lfm߆[_~e츧6x'϶xVx}Ǽy秧z믯Wɛg>r5F朠aG?}ߜnȏg~?z`8@ЀD`@6Ё`%8A VЂ`5b T0[UBz0b0$`*Z];? L?-,o88E*&lP nQCRJ.@(dC$ HWgKɒy*eBq@#|9QNa$ U Ub+JFRLI P 2!ъԦ|!0 T-1],r8MTJЃeljT:UVժWjVUvիӤ:9VլgYպV5mk\Vծc]Wկ`׼ְh=lbj*ֱj-c%Vvl"ngZbF2C@PΖlmqNVo W5n'Uwd`Zr\閕+vM]^w%^Rj]ٞWu/o|]8pdC .`2>.#,aRdzWʖά> ;%<1 Jbб+A1U%2W,x*1U{5NawcXN&E~!O2%G2ɠd-V>e.N6\e5wri 6'}cASy·rdlvM2K:͆|i2x3=)wҦ^3=cIԧf9 k(K?e{1][׼r-a_=6nwp:tlRۼ֮6ms{xÝ\{|MTLmm.+o{t[f7m܁=xoۅ7nۉ϶⨽xi3.ڍ;\;ّKٕjleq\7s?zЅ>/zmzPOsA$;{2$D"=Խcң|–A>idzB(")r #V X,V"Ё":CB8:yHA:i; 녏DS:3BcTWCĕ@A;RzDDHsJT Čɺɡyw(yW"0Z 㼮T(ÃG'60>3ăyER#-JKVk S4)$dLlԘִ˖xu0!nt 飾qb>>H@$M; B#(WOk m'#[LΟd RH"Jq:A)AA*TģpLP m%r"РПwȍOT=л,P u肖x#(B2O d,R$e#|9ҕ8%OQ]c"/HR@C5t ),ìA&ZLZSO8BuCR(lz6T7yJ e/nd =(򩕐lˢ2BP:0áՕ8ьlD`xUTJVDKԝ՟`}^TbEdEcUfuVagihkVlkVR nV^qǒLs}ƒoU<$Wl͛HiFy d۝Y5Yeٞ0Ś}֕ؓeZmZ=XX0]XH=EZ:ږeڪHZ!X~%Fۭej\x$tdmWvH_lHu۾ۿ%5ܐw]\eǽxymUE̽{I\ȝ͕}\\}-]XE]Н{1uu0\\% +0 T0븏 I^\#99!= zޘ(Xh9 _+}~؋P݃hv ^j (`hI`gy Ȉ( ,V`Gh.Iǘ ``+`[ @XVeahɀ~zv0f&N'&()*ޕ 捍Y@HzX1u .6/cc2c3~cN_t0uXQf+^GI.^Jƈ}`hۈヘ2R.ߩNnO>>e}zhS-,H6eR |Cdx(E^l:--fiւg敘jk6nVmpq&qs.gk^ifw6gxynnn~{&}Vg{ggyNg{~.hhgV肦e&f&vhh菦hhz荞nhFiVf阾FgvaCbp-.e;@FegvNVjnUΉj?Fe;h I0chvje>ᅬZY6 |Nj鰞뤦#^78XbKd>l†d&&P XQcO<`^aЈ3\`#$NĆmіĆj6 l Ǝfm6:4 V0  a"6z.P]۞mmv68!;A.> Ac 1m(f%#Kz^J>bu(TTN-+6fծCj*RomQفwo 0Ċ3n1&#Slrɘ7sYТCmgԪW3.jװgMg&돱3+Ŭ 1vKlyo묵ފfl=; M1)+:O<|2):PLqqȤ@[j1 d1jfS1E L/cq4gk3>?c0Dl;"陲 *D5<S 5y7jk-7olIi9Dy8)ww7r~9fv:}: :9ӳo~;nie s-2fR.{;i:.z﮲o6D[N[:e_鬯~fwٲϸ}=?Hʦ>}kPgxTԠFA1 JVLq?9 pzz!ޑ{Z0ֲ]{a3eF<"w'-чgV8wYAm1b6) y);:AVafV8B3!&O|^# ɬwr<1g]dޔEpCl FRQffJ$ˈIRTcځLڅj [GL򲗾:~Qk%-fȀL ⬈C6 ͳY3 lRTjLlr 2n:)yҳ'>}'@*Ё=(BЅ2}(D#*щR5h1d (HC*ґ 9J@ҕ.I Ҵ6=cⴧ>gb@O)H,N}*  P7H؂SxL*ֱJļFn}tt'p+^׽~+`+=,b2},d#+Rn^ذDz,bYݢL1TA Z~@ &Aua-pZ"  +Taj4ڒb$%8iI+J%H,. 3AL8@[R@X^/<]@(0lBf _R>Il`~+b  Yp3X"x/B*A GSgjJ/K $Jǥ]ru^N` P-;Z,p2A]RL¾vVR`3j" F[$3+ G GnM4C-QԦ>5SUծ>d^Xڢ()Th1Sv BZC0e k΄_*1k:M̎m-Tk!6-m_ȓ5,s 񦷼nwm/p淶x&ݓpwK|TwoVMQ[#c5nfқ]x4ޘrhţ𐫜?W6';%08D*x%T_ b\׿z:\bgThVoեoJ rHGz);N;M8Ytk ?}{: p;۞?'SJJK ]Iɑ)vEOX >VqU>s>/??O?/??` .2> % NJ ^Z ni  ` " " B ` _!ؠ_;J;"B_lC95F,,#?#x!0@-VfՂa"_!"!2a¬B!!Jb !n_'~b(:,PK-% CXR%\B%XB0Z%P V0""+Nc¬B,Ѣ-"U b/!:"X7"++@".#IJ$_#<܃5<=*?D"E^E^݂GF?d#dJ>J&.GѤMFd@J^$FnO ?B,_,@D&&9$ #"ܗYXJ%$% d1B42%\"-BIUZ%VVW B;#"Ɓ:`$QLQ=n&Ŧhfm&K*6Up:qZsZ%UX[\ !]r`5gadu&sIza:8*cTƟO#e2>A4,(h_v%li&jC% (v(Rp"DnhF%.lb>T&(h臺h"(΢"g^pw!xQix&#zgS'I'A_` &bBf=A.+ /Oi("C/lDl j!*A*jB)Qҩz"ҧv"£B&VRjN*Ffj䑪(.#4 j*N^jKAn),pP­.g*N+VkL҂?֪hj++f~벦_)ɃRZkEix"yey>2*^_®æ6,hF&k"Ођ&AR'0&ֺ*yNՊ-:--omuOjg#pgw'YyRBavSަ_g`a: &)/,6$*%afa.mhh*>ԃ>H֤&.@nҖ"~K&',薮>&8h6/ Җ(6.jrHc"BJ>3"ᖥXV2cVyVz?d^nQ` 雴 b2^Pnʃ#a#c0 C#h Ng+80|pðȰԖh/h-w5B pb0S7o*p&܎. 0F2%#0c0'#0 ˟묜q q/ ? J#_%[r_&kb%42c6')r*w|*_$r,,r--{r.k.[r/?na^+/?r11q22p303e0`45_6647si88g9/(:3;sd^u;C{3>m^I_?;I:A=3BΙ^WdtBWB_}CKYH#J-4A>:NH][T]Wh]GE˴P/tTDtRSqR4IAz<5WMuW5Y <5ZZ5[[5\ǵ\5]׵yZa@U5`̡CiAi@kIAmn| #wq%t)_Te˓@K4:6G6kp1A r1sB{IuYmv|M%Yu]B %\ X„hRo)HtkOwߘWt-zuQ|vםP„%PynC ,75;yt7/477;#5؃}Dtw]Xmg yǗ%7DvY‚C8=eN4t8ݝO4]s؋Bq8vy%uiDh'XB F4A:89uOy00(@ WM|u7I%y7D4xuQB3w=-5tnIʈYbB™A)S؜v wv6wypPTww;tFm2H7oz h%Z,,Z=1@P?XI6T%\fS_„1r:#^;1:;'w 8CWKP[a{kuh;4;KFg{Ĺ{gI}_#q;Ⱦ HǿG{Éj; ;|}H|P{X|x`whvp>KSg>WL=c>I|~~~K>^@>{ң{bɏ;< 3'D4B9x%?,kcrxq?w o|yDyw?|{cGG7???@8`A&Ta F8TÊ1fԸAH'MD%ɖ/a*T˙5q'ȝ=ʤ8ŠG;DRO:TTUŚV9~eIQVÞVmKmWs.պK WCx Ŋ z)0>~"n߼ 9xyѧW}{Я@q9QU REqvRH Ŀ<l!P 1 h# :b $uPQLQYl56#[PCI9CAFHdq#LR%[ѠvI.]b hXTy(6HǂD-+qA&LS5l0hA N 0%ұ<3? TЉ^QȢ b#h;뱃 晇8r#AA UQImr+{e:ǟÔuJU]y_ Va-cMVemgVikVm60 dHAMWuW+h"t"x1{ ܒ@5q_vX`ރ2nlJDXW^(aONq4 aY՗LME♉.Z`FikA饙v:饛ijƮV鮣멽FAЁJAY#q"wn[}'֛dCd;8T\sfex)amjQO] a]iq]y߁^[e \7A!bCJ`rrTDGɯ,蒫>dq"(κ{u=wJh\p(3 De 6/ D,?\@D;C:PvL`BlCxPL0"O,?R  DPZoP9HᲉBJO`HOćpD1ы FQ8fH#*?* P*݌KGR=2JortdE&8Ha%u_"$ѸG(: p(DTÉq4cĉvHheqp8.`G Vv(R,(L9Q4UA+Jy5#KmPbD7 |sfDbu?(Rc=g=x%)09O^?!E1QnGAR%5IQs H)uK3c:fko?} =i"Hōn@rS.0FG2 UTjyzUTlYZYFukéW:0\Vʕ&U8_k_;Rƍ#lb!YNe1YnEVgAYR5iQZծmkaqVmoy[w5qQ\.׸es}\NwYt]bWM-w^҂Wy^eovYwos sw| 89!׾^n |\' fpJXm 5| lEh ZV֙g@Yу,fZB+C ,8gC.϶cVh]JyTjy\~kQ1b+hlfъ/iG<³(&Ag vm,pҶŇCgٳ=غO^2F? P+xԤ.uOj+0-'f+h.-=Y;zďl7Фŵy <0oH<tL{vmm''ՠt ~[.n}t̲f[Vε cǎ ob6ho}6`ұϾ_ݴqmV ܐ7kE~r6A]r#xi.%{r\>W9ωs|H_ns}FWzӱ.:=Eե9zԽ_g{!`^{w{Nv<{Nx|s^vAogߍxٳ~|Zϒyԟ8y1=^ôg>{O_ߧ~0:8h: }pKOOR_޳ezz޵g耷e?iˏJ .1&k? ;Mhn@k ,<+XVPVlC:,9fa Gt;gTKPwP g )  kKkK KY 5P]K01&"1.2Q717O =1IM>qS"dqi lHAHցDf>sq[djʱ Ρ +gN[ljj7 "Qd1%tn+Б9΅Q_ rTaP CDH &""Rk&!"J"" #RK҆/Q$E _'% rU6(r H 2%cRZgqo' $f ϑ([')* &r+++2,r,ɲ,,Ѳn"*-OD.Ҥ ޡ./Ar80r0S 0dHg1!)'"23e nCH3A34Es4I4M4Q35Us5,+^("63b6i/n,r'ls7;7c7/!s8K9B999:B:S::50ހ\&q3)s/'S-1=??]@qS@]@@ASA, >Hd?)*tA/4_2TC7^:C?<;4>i0( DtC+B`TJEcFgjFFstn4D)CI85 !AX"4lʊ F"ƆJQ@JJ4"K4KTjJ%G3KtK!KBMIJMTLLMٔGMtNNMOO PG4QT PuRRQ uLSLAN/REIK5UEURUSYSUCuVITTiO+RwuUoUk5WI"#%W]Wa5QuX%X#WXGYWu[ 2>!HwS\s\HtD\Kt]3TI56HH/U/..+;Yj/E5D.-"V-&,*V,.)n!2+:6+>-VHGV]] @MaKaOeYftc"dfg5eߕg/TggSc}B/vfVA*P56jՄxNj4H $/@vA&{p6z} {'`e~ҩm6hD} o{CV6H"pp{WVv6WJ'rg(mGV #96:rD;;&l(0?ch7ʨ)T uCHxi6dxmy#ByחI6$"z!z7{e7PX2׋BD8 wv"~w"(76Jpc#z}("0Iu)b~Ăa~wYwCx{/82x$)"TX`jc|%"C/[h"w8GވmeEpā*"Hi#p*wradXX XscU{iv(X0fFxzwrE@wJg8ɓCS4()$ɓ)> B,ِ: BcC 0FٛJ Be$- H.3y694Xم]9{LX$ ܒ "i ę "LD!(dW""bix%"Lf3؄ȕbם4Xy(99697"N"'b kY"0oSjNâ)gicrwIZ6ΉR7lj!ɇ " "<R$E zjZXI]}^ŌcilzS"a5קgzщaP^Q%t)R0}C!t7Iv„7mM7;vc#ng UZV:6׊$ 0̰c!SW^ȏ {۲)X{ڮazT \V|D9KkWķUSD;{/"eƉF{51;Sż;\"`{R3{;| <|9WG±rs9xp~r/elE|IMQ&d_ahbUwd~ Fj2wm+hXfe^a$9yHcAcT餚[)tْWyyv*蠄&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/]ҎL$SHIRMф?F9IE" ҁ{ H2z1 c$ %H>RV_@F8җ{A bRd2FhZ响&Aig~f5yn~ӑLTMPSRDƒ3A4Wس qB-hNqӡ\귊F$$! Qz3:=LQ%5MΆTUAIyB2iJky&b)?@b<0 %Ph/H2$DF4KAsbc&O6ɓtU)GŚRNҬԨKIR (Ψ@,  'ATc$H1Bĝ0kLh'<-ANڧ6jaZvmO0aoiK` v mSDpPIim.uJ0x?](;nl v¼WםճD$&yոnVj}GW%p8_Wk#+En) TCNeJ&8!hmD:ˆƜ& x'gm&L\X34^[wHujb:q%k!).O1XKv J@!U7TP1~Rm%j*!38w.+^#N& 2)4#P)u"Hp&zI]By'GMh ãL,At1f@HjodZ{֝5g+Mb/jlt(g~6Ud8 7)1#nq=7[lkcYr-һ nh#Y۱>;d d57;C[7{ Gq(OQn0'ycN|6Ϲe8{E?e'ҧ[הP9E99:;7S)qZRw.ɖGhq dٍuْԈ/iit$ Tp #Segi q/  /P)i)87h]ArHyiyY99?Giq9)kWqiqڹ@ٝI) IᙘٞwɟyiW7T Mxi9q" 1Yq 45j77Z90ڢ3ڡ?ʢ2W|CEN)I1z>ڜ/JIqũbwiq.p Faʍqqtjw]ǧ z)bIhZqZTgʓ9*y gT੥ywW)gOyx\YXdZ5ɦg ٙz{zJM Pqyb9w :*G֊J|V~zJȭ؝ъZTpǚ:j~HׯgiJ㨭$GKN(>Gg q ّFIFp9J y9ۨwu2+7TX;[}0Zq%P#سPp*ΰ8/P3;DΐYLb'GVZ˵ٚ~tc uDW{g q(Gks->[WI9wt wvrr\G;r{r+ tK[rs+!׻ks{0{q(K)+ ˽ +Kk׋ի%t˾<+KkƋ˿t7qJ?@]Ce]5^uɽڼmٯ ͽ ҡc.s .da   \?}&] #Ll،H[fϽ N-H Fj>>GNIKMGO~Q^Gc{iM݈5@ #?v=䟥SNikNmk,Ȕ tgop^m^{~}.n肎,z^ɗ^ȋnŦ~=ii9@ C BN떣6m9 딳?)-3l>9>^~؞튣k^EJE\b  ! . h|W  N YaOFbAP/P A 0O2fTemo G3? }@CmeUOO P &MVL JMUKIktwDn_V0]rImOV,<$>T{?%0 O?@! #m?Ya?TO5RoQ &OX .Rd|_eePQaSfy?Qd*f"bbFfASf%D >QĈ88bF;^Q$H%MD R>4͢ɠI"*5X LXJEjણM>UTU^ŚUV]~v&@9,ˈ9-܉:uN&j1G7{6XB?H$΍;0*ZhҥMFZj֭]_MZlڵmƝ[n޽}\pōG\r͝?]tխ_Ǟ]vݽ^x͟G~{y+^|(0@mD0AdsB /İ> ̰C?1DJPJDOD1EC#%W1Fgq"*FwG2H!$H#D2I%dI'2J)J+ҾIf$K/ ܲ/ǜqִ&?4HIҔ+e2s(d"3xhM|p8!6ғczOL #)M SAmce:uhAVD9#8x:]5fiMVX1!u]&zR G:pH!WD䜬G5"`H `urX]dV%{ֶJzX\OEub巵[Bf #"˟[gIҀgJ%X}37d`yufḿD:ip16k>U,MM$0yɺ6l^^3J{jQ؍䎻dFlzՖ6!*)raqAq\r+۴Ut Rteh;}eeI`}ʠaִmp/xx=, B3`dZiΚn鏝 r=yCLzp 6"r?~HIbiLY*sJ宁`XCʀћ̭6:)Tj”Bg-a e8CІ7auCЇ?b8D"cSD&.*D*Vq_yE2md?bU4ogDc40эoEF8ю&HwG>яd 9HBҐDd"HP#IJVҒd&5INZre(E9P~De*;iJUҕd+e9UҖd,qK[꒗t/9Ue$f2ALe63tf4) MiV Դf4fRY&1 p4- H`$iNtΓ'.^Ӛ,'YBm"5+q{`’P2IJ4d- [< eC+Yщ:Rx 1.zMVb +.GPH#ZR* X/NHBL+0RgtSOUU-%ViZhd-*>RXD9#bd+\OBD )Vjd\VGCT\J‘6DY+H82 ]k[犌^ 'QTZUQ{޳Tk/[VҔd}f xX+I58r *q:Ү|By&WT.[ع(`;yYW}db)ZG@(rniXSm_ZҖ+* <_*u#5 Ñ0]6v䇩 aHbؑ.@2A àT& ‘p^*BTP-j_"' /$+GCd)?TvȔe&oy ZR.WyX2e4[ّg3e8lN3Kg< |\9w$m>̈́AWZrֳiJ_̎#iF{bN&ϭnܪ] j;Ӥ~E]r#U>ɵqG]jNY׹6ua-Kkl a0ﲡEٻ~1Cr>6 mn~Ĵ /kp2o'Nܷ{11j紡+Fďr=|ĚeW)>,9QM 0,̡ P=:ғ>U* tnwt5f| %DZZ7XD1uJܸ[/lg{%5 * g[Upê7Ei(L* xxV=<)!ţZ}SRh~3>9o֘>_#D@;@c !?lT 2aA(RlUhP(!$Ȉ)$TBX(Bh*+Bx3)/0/$21DC3TC2DC1\76 C8$C, <<>?@AB4CDD =TFtGCFIDEJLKNDPDNRtDR4T?+\DUFDX>Z]D^Ű_`FU$b4cDLdV«heEeQ]mdFonGJqr4SLPlFgF/Iu4FzLEtC|$}GA\EĀ$H[QJqԾ厸(ԝш5L=huU]5Ŕk]{8ލHݲY޾iލ Ma3}])ۧ]򰝁_Pa߇k1Au_(``}@%bh q u |}x I  q8ٔ8 Ya?p } `3iNa Lj: H Vr :k &FX➕r-: b ؠ0ޑ$~x/ U+<>?@A&B6CFd\eDfd2٘!lIG)LfKONKOR&RdSV \WE>Z[\]^_`T fa&bN~O脌@yHf@jkjffj.fc mfdn~nms`v>h0gs~R(zRXfS{N0(J\U@@HgugzotPx'ufrhlfۘg鐞`xipg`i }RxR OpU)bTv vs^馮TRqiqꭖh.@gvj魦x&떶O`fᄈ@%Za%2} ^'jFt@V>Sӱj&n hhʖMn0>f0z~^dUГd@U6.mXVRpwpwpR`nnnnnˮv.n.@9qVHf&Ff.tikb^d U b@ٰ~pvpp> shynjlnllqp׈hg_^/ iwڎ"qm/ .mϾp /nkm7l rN*ogw&rʞ }Vӆ(q>m_ 7b8j~j/f//>sP%wgpi.j,O&nt'pg1Ok2r(Rg7}.NP(8v*Wq\t\'AgluH`}_?naW@^gfGlhvtlknnLj>U$r pLj,ڐ4vx")^wwIyжwWxѮ!whOgx.o9x"wuwڸw$jԐ7GWgwQ?y'7zWgzOz7{' /g_{ׄV{/{/{{y|ÏWgz37$V@MM0G`(by\P1y| |oHHy_ӏy^>gP\P}g}{}/Mhڏ/}߿yȄ7~'~пy}/x~}yB/y~}7OgL[^L2b9kLa'5l/e,b,38*Wl%L)cҬif˙0R+")PyS'ԕRj.Q"uziiF^nm"[D_AXԼ.Œ5۲U$p 2ĘVL^ {93ř^4Ex[l3( ڄdUczngIJc}`n* w\p $4iF\\ߞ(z8aճ>ZN#.omtk^O )`iŕ( [#[Abp~75PzXaT35FP$eSO7!8FZ1HQM($ю؏@$d+Dtc9'ђM/QIxyi&kJfӕ>bk׵Y"  Şz 8QmG58'/TWwN$VF$%:2 %㥤*^r(ʣu̧J'=mx*fjئt i*DɫlD:Km? .٦K W_nV^v䑙B*릙.R2[y' u~z5o+U豪N8PD|S7HLqMLUslSu yϱ˭DMz,24s56\*"ꪬ[$JL3O,hCj|nhzuMc a CӦ0GNsu,"nM-bP]arGA%eLؙ_S\ cn2AdqܟK:ݤlěk^?S93ݹݶ~pA}Ͱ~y~{3V'A|ၖ&E pMF.oAycTy}OK#4pa?MH*A'ykL JK ,H]2)L)w8Q`c I15aD߶D ω^" V5$E⊙.>1J?+YD01qTSh)yDZA$iE0:鄔d{C0&T>ReLaJFGs* Bx2DYrK+!fɶ >24R-94Ę|HC532G4uj:ÚlEQ 2љf8BNs",X:NE'8#Dp$Grq#"%cdɈG4(JovxFĖ̥bHʉN!IARM)!b K 3PMeJIc{zP ԗ51#O**MUSU0u/Uהռ`u(9jR*#BdUY'?me[abVs]]Ӛ׳}_WT]2},d#+R,f3r,hC+ђ=-jSղ}-lc+Ҷmg `pq+=.r2-(nAbsr.x+^`ҝ u;}/|+Bt :/ &<`*/#,UI'\Ps= W qG,bSeq9> c#8qc1]^B>2%3ɲ-ykd'SV2\b*Ć,1f>3Ӭ5n~3,9ӹv3=~3Z_>4/ю~ IS3iDczӞt; Q{ZԤ>uMUGծVt] Y7Zմum]_׾=`a[~0Y@ؤNK}T0%+zm^C{ۣ? pozδϝju8&܁I"@"2 XPF/`4E ;\.8R*PB.:\:$oQmÚ^ <$x_@"o"\Fq8BL_:+\%(w8Nqk攎y]m/wM||EW&`bW 链%H޿v ;p c{Q~䟇?ﭷZWG xc7H1pJC~x&d ~R(Yd^&fffFXWWl&hh&}&i&jj\(&lƦle\(Xmn&o^Wopgh/vq&r.ar6"sFtN'uVu^'vfvn'wvw~'xx'yy'zz'{'f]ZuW r}f]obǂueg(zUh$$0, eVUd@/(3C,7#f~Jbt=hh(hzT&4TUT(T/L/s-(>)剖(h^i:vZ-ZM]ő.*4i)ˀ:)n~B^iVՕ_(1T1800W()ũN߆AVaRD4V80&N*)ci⩝fTbU6U r]i6{'-^Şb).VjvUT@6A7^8N8N10$+p!'n:+]'i^+c [gN+`F_RG:N܆֫Npm,fn,v~,ȆȎ,ɖɞ,ʦ,s<-Բ)\F^vYf}b-v~-؆؎-ٖٞ-ڦڮ-۶ێQ—Y,&t&h+DŽ6,U@22&DHRż՛U$ :Sggalq*xUTȂ&˸&,UTd1肱bB>&tj&$0^.jTf+ W N, /)]oUꙢk|*&RE5'HUxoR񞬯:o-µ U$B)\&B"fN)S/oRkڪ7tpD~=*ܨ^X*"*pvpy{a"(0nj]*r(@Up/Topb>o*(jAVE/zqǝ@eV=nUߎ.z1&[q#2h[* z+%B$. ,ڰ&2?,TX$FZEUU,^nSiv.02ƭ1'2/3373?34G4O35W5{yټ^37l(*ls7b73:3p-k:sɦ;3zɳ=g:7)3?C)p]?@4AA4B'B/4C<4DG4DDWE_4FgFo4GwESGH4I4H4JJ{IKK4L4MtM4NgL4ON4PO5QQqPRtR/SktS?TWtTOUSAU_TgV;5WwRWuVXWOO*@ )DuY\4]5N5^״^L_ u`O5Eׂ2D&#Ht &\/ 051`cC@%%D+` \?\ Xd/Ce? 02B] +4+8oSyJc#}+3=;C=?Cg8X|HK1?׷y;&t-={=S\Tn;x3+x#'w7ۂ&|kGxCx{|{c&&tx7wn(=Jp~po~o~f`aC4&,&8bʼn0fԸ1"ÎAYH'QR)eˇ+]ƔYL-ki2N{rhїbthSLFTE^UO]u~kSXIѦUm[oƕ;n]wջo_=?mTP<EBtkV+y Q>J-LܔS+=LQ &M=5ͶJkefLU#m5I]̕W }`KdeZմ04+vOoGJq%7Ib PP4Ѐ]6l罶je\Te2cUfeFyu)r!#a"8gxKbEdUSyeQf9goY瘉f隋Z樓z枭vZhZk>za6벁譟m>{՞nyFl|Zo^< wpIbQ|bκn#'|s+8Rad#u>Ru$%%T88'A9FM}4)Q)YE*aKYΒ-qK]/La41LeewD\󐢓7ჴ HZt e"#JP3pc83ҁI&qW$%l;9Oh>tH)(;1Nxʓ3?4}3(AՉ?h4a*Q1E3r xS4 I0bh@91 4@/) f3h6)DZfэqCFqYĔFxfC 0U#fE+j&?rk]W~]iZOL@\1X5#u kň0 ig0ڋ0=&8D0#k_[^#mjjp0X-Zš`(ᯏUMqI#t Zv ٮmbui]W+&/w1Y# mF`K[.-0u[FZ5jO"xʂ߅Q"c YkFNv#]F╎.XWabΨYJh@D~q9XDSs}R |kP,[΀N oVMsGfh1@f`y}r\#Y3\@o>MME+#Vh`2})@ј`F< jQ2&UM7ʍ~tgԋ8pˁsaF7jCM>M,\#`ވ0\_`&ƮvX#ݙj6]wR?[wF|;#`͛nrd@=ewAм6X6F|pnQ.ZcAjd&rE}cd}v8rJ%`in(O:ϗs[#QOWju`\A[ ͨU?d@ZW͌X s0Ս肧NPy? {nb)y!nw04|Zs5vo~7џ~w[~C*8pW >IxCwu F&Pף25(AP39')S7)]G*3*M)r+#2b+rR,RF0mv| -ڔ`@>-,Q0/*2+WCR"QP0'0 1Q|22,'$_3b&3 x.>3[3B!2'R2 "aQ7Y6117W2($2W8a4gNtl4N06=5!e;7R[2Ґ w= 33s S:'c@>Un^̢HoO9 3@=>c<=d<cn:gr6ks:otBqwFryEu~AxA|=;;>B?;<>@@AA@BCBBA@@?????????@BQݸq߱᪟ޭׯǴs`UNNOPPQQRRRSXmڞӾ̾̾H*\ȰÇ#JHŋ3jȱǏ Cȓ(S\ɲ˗0cʜI͛8s\hΟ@ JѣH*]TOMJJիXj**T`ÊKٳh~۷pʝKW-Au˷߿`6\ È+^dDKLV{>6ϠCڹSK^ͺ5=0jظsm6En F蛷ȓQ8GL]سqxwbul}w>Nx!'/R}th}4g~ 6y !j: dDAgbUwc IBL"F" $#IZ̤&7Nz(GIR2L*WIT|+cIZҖ̥&g^⒗ &,)b!$$e WTf$LhZS҄$5&d 5Nq>4'4k넧9!I'$Cyaӟdd RD 0bKp$`:3MB JDrh. Q`$Qsᢗ?*PT#-I!*QH/$+^Mԡ],4RʋB5xQVtvdQ;IJҰjdbʬnDBzײ <)@3X6JTӴlH HbLCOӐHZmbE Vͨ+RZt Zj#)֬ݭbG[WEr-&֧ѕ7#YZ"nqv%CxJWhZdQ]nw.t+ZW@/%Z/$T7hVEpM?~nwNDzE{%ZֻfHR$5D ,b%_k;CK_H6?nBIXHwFF aYȓ$e&sʜlI1'e/{)4+&,J`n+8"ÙϑGMX>tt,g t&%#ZƐq~cj>/ 1tnW'MH5z%Yކrڬ6T!E7Ԩ&'kC4g% ,P׹V[ l>v[I5cRזlmqܗD iW[t$Iegzs[K/0/)pIvn@R^$7 uثf | $7$򁜼yy]|u*on5 sDʇ^tXÍv]A3ӕHnHoy[AG祭:_I^;o#SڝI ][?Ox߽wOwvO <[~r:S^vg{>^yu{ބ'_Kӷ^ oI"x?p_}/_-w|OrUh-,Y9ߖ9 H>ے~#뛺,?$ϏIoY__}f IXiHp&hq'IVTo X hpgGJ6I p3NGjemF kم}dd~Wikw?VlTnS<8zEpIp`QgMHb;؃MMs6PFmU腅VKMP(8\D ee4xA]_R^fnR|o@~I\Lhl"KuStbu9(cd tO4] ۖmOP%Y!WsQQfm5u`v( XZOdHfx(eȸ8 H& 8SXVX8mS p荑y}IISfIUphJ$@ԊcXsqITNIJNYJYI!9IiɄ$钵d0I29t4iäē瓫T@9]C EGiII8KKyJO)JBYS9U WYɓ[]9Qc)egiQkɖmSo JqMTv8A,A A{ |ٗw7yyzY闏% 0ii%yxٙ9 )YYy}њY2A y Ͱ͠ ٜϩY9ع9){p~ٞ;B 6y\ؠ@@)7Z?:Zz%p ;c ð q P fPoPo'y p @&4 Q @&Ljb b j`bI9K4  3uq @Q Qf^%əZp $ڨ%ꨌ*,3u0ѩ  jzZtPt, נ1ڙz靹꫻:**JΚ FV y ppOxQP*yiQ<2@ lsOr:Zp`{*Z}I@jJ# W*-* doe %Zp3(O P &Pa%,@z0˳@B;VIHJK{LPHR[V[T{Z\`a;d+e{`hM˶n۴ao;Ot{z+{۷i~;n;{g1yI{zٸ~˸۹ xk[R{ F{y麅lۺ;|+d[;ƛ{˹;i\[ث˽ ۼIP׋Z+˾ +K㫾닿u ypĊ <y <Yp \!L 'L—ʊ|) #L%3\"|@2L:lA;^+`N*9%菞' y{\>ꔛpN婋ɛ٠R 1ĽP ' z} };%ꞹΟ.^Π~>^>C ٮ$0*Km3-^SG Q8JA#Dm +=S {JV:xװdm]fw r 4oY h} ?:rMqN7]L؂Gמ-{ݲqjYO[x$6?]koN:i٤ -@ !|Xwjbn kM*MDORq`/ TM}o~9yOrB3駏Z?݈yi.{2 1QanvD*gOyn*?ojMP+H@Յ =y!,MLba3߈F%MDRJ-]SL5męSN61e A#p$G UTUN= ZNR$)8WD %1!ML$$ͼ{F#Ċ>Ydʕ-_ a$[x]kTj֭]דnx8qB߽a1KJG\r/7\tխ_Ǟ]vݽ^x͟G^zݿ_|ǟ_~0@$@D0Aɧ|P0B '0 .004)Æ<, Ą2԰B I,1DUpF Do1رCI1{t%!SrH&tJ)0$2KD<"y&$RKd M!M3߄3N50QS#7DR): >eQ:ЦIѦH'H'K9NTJKʹQTOPALW;t4W]wPNAM,M5N j5Zi a3a$Qn (QkE7]t_ܐ\BJ"m&Ou_8`&`F8afaع&&b8c7c?91dOFdSfeE^eg9fowc{z矃&z桋F@>:iKni=zj3jzkZeĕ쳑{m]+Aj.v{zo{o:po8 ^,dye6AǍ/1dr#r9|b+@Ϲ W%N8Ƃbbu[o"rA ؏?&#gp]zeW|)W*WHٝb,jh=4&'+E&>U}X`AyMpG2WhbbZE4FTL,:xt٘GA/nѾCѰo6 q1Pc+c'@؄Xpfk+8>݁ #D(JqH!"rJdbƨ؄(6pn @'?}PO$)pb\2A+Lψd C2g=4$I3D.$dI8F t'A?,"1Mb&'&pbXP9 ØK[ '-[͎M BQdž| ӬḮX&žor4gʙȘ;xs'{Ŏ8v=˹~":ϙj.pӬFDϋ.!=Gҗt.MwOtBQW%'Zԯv.N:^nX.[Z:e9Hұυkf[\Wl*PW(h55[SΞ,a}Yx+'$C24y*ٮntHvBv#k7\wM 7BhqYJ>p\T!O@NT,ϴ]󏟜ّ[sq;'yГsluX3 I@I&WtOYg7u|W`_3 |W9kZwg]Thxͪ=|cw7|%?yW|5yw}E $!0UA נz>fo{"}?8A!pA Qcɇ>跆b~}{AUƹ?~{Z mLJJ?3i8 $4D@,dtl @ @\ @$DA<dAk\T@AB!D"4BD$@н ɇ@&%BB-lB/.C 124D4xI 6$B;L5=A?TB@>D ,D$Ct 19|:x0SUڸVЄҰO،U0r8 rh ؅O,Є! #P % тϓ0 PPe58wӐ DSx@(Q@Qτ`:0@ЁY8(Ҭ@ROP`RxRRZ 5(QX.E  h2 3S(UR!%R%mR3SP5@5hSpdK`%1Ո7ҲR;RLH TTL%4ux8LxTSSLX} Jm[ԆP=]U-m :M_C_Tjք8V.e- g \]T^(xֲHu bm efvMPpaePelEx Q ؽi SW%%(0: H=V PP#aUҸ، %匒= 5XXx=w ,TI] [eXs;a%ՠUPU% 8ׄZT UZ1ڗ([ڱp!m؊?5 .Z0Hڥ, ۱M[}uӭ]݈[S۠Ӓ[ u P @h;dф@R yOЍ6E[M ͋`]Ѱ حhԐ]q5Q]ԝ ^ @ ]%5EUeumC) kl? F` XF`tӐsFV99]`f~+V9?ž]?aFa &b.$#~a%vNe~)^ʹ(0p;*bm57~~ /RdM#0U'-5M\ݗ7c:FP%چz[AfS~!䒠D3kElMVnՍXrd,.j`v:lyfid;k8:j y޵ kY~&kAk#]#깆~ _BֶM#V9 l?lk`8{InJӾՆ7[Fji;æTlflV믞j7yi)A¾i~n$AŨYǖj6^F>EpjVK鸳niM;ioo+7GWgpu p 'qGWq?wwq  'ro "WO%wr oP'rGrG*r'-r/p0s%23G%W5q@>>9p #҅ApB( sHe]P#Ft`cʗd|Ƴ#mÝZwOG%u4U(p "˩S^Nۚ{-A:o4hO_D`O"ȓ&8 w^z{7FeoE@X$B*^0JF_B!b +BH*9RASw~y#A(aYⒸRᒴ5i}Iy&R"Ci`2y!(IBihoG\p j A爟XnX壓*بVBRzJGg=Hw:gv9jw@5 e d,GAtIәD`n*B64Jk]12- #-hzHyPF-TS1|nAۯJ+Õْ-""]r`QDŁqW\2|r(J4dڝ#}K^ELXkh54DlA \ m ),wtO#'d&iKYEGi^w$bB6Af3ti;]bUfeb97~Qu;.2#۴2ӕFC!"hhɊ&ӯþR]9-['ӎh7y7 2;|/b0U$H/ī ÇKBs_o1/R.IJ.U;A3 _W>9y 0*o{ A U L/{{Z 䰺PIC eD~(Ɣz]ARXu9\fg4L E#:Y/%&#Z񋧫aX!q;f#wؓ*:}d@ _!Hrrt$Jǰ@,0FMNd$AJQ;d /iER$4+IKQ-9D%0)a<&2e2|&4)iRּ&6mr&8)q<':?S|g/a 5 }'@*ЁnAГgB *QcՀh5Љr(HC*ґ1 }hГB)(Ick\)Nsӝ> I_T:*O'J dH*T*թRU˸*Vխb PJԣdIojֵn}+OuӠ+]׹.}^чv`}BXpgSR,fUuFQ3{Ι֔7hSղ|(lQNu8/d$H-p+V fܲ}.t+uvֽ.vr.x+񒷼=/zӫ}/|+^8}@Do.R\DOL1C9Ρ oyC2 1p8cO6PC1 cU{k 'Df<>5! S@'8'0O'OİPx+3 1Bx†5DX6'^fk=,99{1O,`<~1 (bP 79 0b ѓ^/ q">1[&8|8#.Q54k\3u;&_Ϳ>Y>z?sk?ȿ?`.`>_NZ^ _v Z   Z Πf XC>  ֠. !" *a!>EV!`XAXAb^!zJaa aΡ&X! !!ƚ!*2b%"#B"Jba浚6":Q"! b"b%z'b߹P"ݢ.ꙻDc6/D0"# c"b#O$5/2Zc`7.263:Ic.>#1#7f:vc;7㼑4:n=6#3#:.5<;d66Dc=A*d>VA"$D#EjF^ÉdED Cj$J;FGG$=jI~IJdJ2MdGM>dM$4[B$GKRƤN:% +U_VVƟ*WaXXaY*aT\؍QUveҥ%e%& e"!\2 &BfJR Z b`:& crf&& fj&"fkf/}kvl.fmjf f\&oƠgk' &r6ol. fl'p6'tvuƠunrrixx'yy7$5{')8*yQi9@5|&R)<OT@-gw0ZOHYE`]@炎(ؑDe((| Z/-[uNJǣ 5큍)נb.\}\ȥŚ\@&))wA/OHEDy)ZDh]߱߹OԁT)&pqBv5~äen*v~****jp!u^C2*x"bjvYӰZ&6k k"ߴZ1UX'`׌b++~OlVk1+ƫ`> +jF+bܻ&}i.,c:JO8"%FB,ªgM1,Ǿ+,l,j5DV(l,*j R+ll,n-͞kҶk6N-2Vn-v~-؆؎-ٖٞ=ڲV B26& .H\c'xGzhà-ŝ-O4؃1.eؤmO N ri&,؝]£^]rXJaA@-]YF,lRIDƮ&ĮOh^n5DƝ賥)P. ,UOx| ٤(-oD-~QBO~AX^:œꙐ].\~liXƱBq)SLUږ(65hOv'Ԟw).o>3??3@75AAtB/4C7C?4DGD3tBOE_4FgEWFwGDs4H4IH4JtH4KCK4KôL4IӴMtHRDNE4PC 5QB5R/u(5SS?PGTRϮ+TuNSV4*%OA_R!5 tWk5WuLZ4[J5\\M5R't%hP.PBAg,t)tB,0t:AA[uS4 '44B,tb/vAA9:ZTC kdKTvm۶UvnVvovMW4'vT-dAW+Bf4Ԃ(dBc7AAj7`]W+hAk-4BB*t* AS7A_7AӁ4AЁw5OlkA5ptx#x3x wA`ǂw'(B4D6w{T\t_z+4{8twu/B5\As5 5PO6x4yRyIu4/y'p[8?8)B%DxVkwxtA9X^'4'Dc- ÉwyWR5pAAl!wAA|'z,zA;: zW+zto3::?zOG:zOzzB:zkzc{zϺ{ ;/;[{z[;3;KOwWGyAOy[yAS8|yAǹ{w;{;;+ta / wAkA,;A{8B|KҎ45y{yy7/x'7AkAOd~ABy/4;+A;T7)TC=DO>+3F#;FK;4VScBQkDC{K?E??4?} yg?@48`A&aC(\(bE1f,QcGA dIOTqJ/9L7OĹN?%B:hQG&UiSOF:jUWfպkW_;lYgѦUm[ou\wջ(|ҐnD jaa!G<ć0vx1̉+=A.b̝?+Zҷqֽaζc˞7mǑ'׻gC Z^tҫ+׾{^&ygٽW)j}i qѳ׿~3< bF>:Ц Ѧ)B )! ;0C,+qB U԰E!pECDC;9o!,^#'T+Hf[2/ Gn;ヌ\wF.fK[Vlˍ>5<=З&M9u\ǃWom=p_]wq'~w3F驯}>[6~uSg,+6i"tEm! ؿn(dS!p | cp9Anړ`28Ѓ$!BL B0 <`+HЄ8E :aH 16\8E&*QXY$*W4na\Z&qOԣH9Qt\#6&e^*>I2q%Ljs'?ʘ)B2>ҩ2t<(K2jܚ-UfA/sۘI]n^+&cd:ܜ31;1mf3yaJSqD7 NlԬ29lSo켛;Oɳ$e5ѹN}Zb'vm9YP)n'?P{bEÊKD(<эb\b50.az@Q ]x(f(9А((G^Lϥ +4AS\'bSꔧ>^jQ=V,J S UH5 iO BzxQjԦ>57-kjUdz[jWߺ B CO{zW*F C] j׆@ < Jc\*'$M(CPV1o & Z\!8j" MHCbːv2m|W5.Dն,N nqېn7 ݍs+2Ga+[WUbS ,z٠UzE&jgR;CX^~s_z?~?Æ?+䯷oBϩ^!l !Ă ʯ!,Nzln 0<@-F R@BJga0epimq0upy}0pł_np [ p c !0 PU0e 0 G\2#P 0 / pQ O|N"n.1q  1q!1%q)RZ`09QƸ*b~! ߀ G]1Q|+뾄bK Lev$^1ܨl^ܬx񿤅r$xvCusDHԒѠʁJZ|A )D7Q5DhN㼍㢀 4IN2LX/n뺋DzY0!9L\ϒh/†/t#">:%0B T%u/q'}'2(r(((2)$ك))7!]PlC+ge<ұ+w ޔ,3R.282?*,$-CO,;/R*B#Wΐ:ԤK-$,>$11ղr: s.$@112-335l NFM$4^p) >L5%C3R@5/ͤ2es6'SO3'C76ls!2OK2*!mDzˆ6E9:':P/bz;L-zʳLH2ISH !/ <}5E!!F"=C@@FBA3FA"@tBt -31GIz638>$-3PvEsFNV.qFkOL36E[tHUs#s?JDyS4S;sK451KE,-tF=͂Jw3J46635T/yq-=Sns>0PGTH NRCRRR15S5uS9S=U+'bT)BTITcUUTWUT]Ta5VGuVi'lVw"Wuu%"T)¾\WgW&fUX&uuY}YZSXZT5[Kb[5# % *b" dHa J ܠ Z#'(A!*v^_U z€Z#au^ 5__bb+b#nzb!^=^ d%bb1dyAzn ^aVdibKgu6ebee" xi f# ޡjUeYV"Vj j Bl@ (max[#8 \Azb!8!!*hm AkiW ] 7A$:A  *Hao*W js " "6 ` V :W :g%> LW PWb][9n! bscsCwt "w`w wu$dx5YtA Dty u\!ss7yWwO7u[z7W!DaZK}P W|~{y !m@A !Զ`W6n'W,֗ H!"n}u}I W Ar+" xw W| l!vwxՆlaxbzx oq؊ywg⋇ }x8w x8a~ x ׌) w B!a$ Ђ/'2+B×yu5\c] $ v f8p @ uxka7!P%6 gA ؉iU9וuoYiVznY{}ꙗne  79ٙi HaX yѹd˘XG%❫ ؗYL")i`ۙoWـY)_xg y fzyp}qS Ba!dczm] 8x)szxwYE jy[szqoZr9Zj | ZZ:ozw:~x? ڬz :Zz "麌"zڪ9 :g@ L[uZY+x1Z 4{ y!8 ڳoy:?ڶ}[sI[Oَ)[z,{ 2{O {:)Hy!rI9/ VY~ }k  m8oX !{{  ی; "y¡91"۽wuQç1YB "%oBn#25b3\7 a XX9By8s2w]g֍% \ 廑y ¾jzc!; ~8Ǽ̱\q\ <: @̛wV^]9\͑wO"@弳;=1p! (g[ &ƙ 8Wm98!w]3Q8)U ]U{rXn5z z qI:kw`g-|zh7l]u`g]z8ؓxٷ] ۿr]sϝfӽ ֽcރ/ٕާB٥ 8{#^ ږ׶mm1Ցu Dar-W]a9ޣ>9YU!Y_=!v/V^U^鱕MƝ~##~5秾'>$>~>Ǟի>(#^"^! n~?5%)+,5[[|e!*.0X2 =YH>=OTJcYZQSL{W6l?, u2i3U.j?CCtFW3+FGc_8JȰmЮO?[ES%0!lE)P.ѿ\." <0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СD=4ҥL:} 5ԩTZ5֭\z355 P `ۺ} 7ܓٵj]y)wN@o޽{3h>{^µAl[9殬.pJ YB#81՘izOײ$;ݼ{S@|E`VڷMr,fܻ{Ǜ^yBԺÄ+ڸA@G9t{6]@McuM36MNHNX!Tr!~(aO!#nXb"+袁?ŇcZ{A^ZQԡkEu652֔Zneu~rZt^D5ЦZۚvމgSEZoԉhNJLACDaѱaUJjjj kJkފkkQkKlYgl.,> m9+mKn ~ ,Z*[Я z޻ҫ|kB$ k> Wkz tBMtFtJ/tN? uROMuV_uZou^ vbMvfvjvn wrMwvߍwzw~ xNxx/x?yONy_y+y=jF`O誯>p Iߎ;f";|m<ޏ/pOO=SƔ|o}Q,ݏONEOE!,''''''''''''''''%$##"&), , , 256556642/-,-./02357:;!="?$A%D'E'E (E(F(F(F(F(F(F'F'H'J'K'J'I'H(E!*@%+9(,2+/0-0/.12.28-4A-6F-6J-6O,6P,6Q+8T'>]#@c&Fe$Kf"Oi"Wn(Vx)Y|,[~2^4^6]~8Zz:Uw=MsAIoCHmAEj?Age<=d;A>:99:@CDCBABAABA@@?@???@@@@@@@Liخ՞ӔʍøǻlURRQQONKIFB<8;};z:w6r6n5o# H*\ȰÇ#JHŋ3jȱǏ CHȓ(S\ɲ˗0cʜI͛8s\hΟ@ JѣH*]TOMJJիXj**T`ÊKٳh~۷pʝKW-Au˷߿`6\ È+^dį fL˘þK͙CMȑ@OՒM˞MmNjc[;}n8^s?_ḡOOνIwG|{'n}zO(QLtǩVIZJ˵ށ߃F(aEs=Z|E&Qu[B!N0N(a؟Ayaـ 8߂? "EfP'u5p9ț<\S&bAXeGpV|[r^(y%m^hdUʩ袌5^gTgCa9(#̿^@<[2tn.iFCjL7tc#ٓV\w5" IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌@Zڲ.w^ 0yaL1f:d&4IiJf4n '7)rRLg4Ltt'}<*wyEAIЅ!% p]hC3΍r6C*RlD)>R(EhY.%E)xzC ?"ӨHDky>(-OfRɼ*VUuj*X)ֱ~Ma1S$!Pk-3 F(TT@Zj! ~+Jj֩BQ M2},F%;RVֲ&lfЊ]i-]BQjA%X&#Sl5e+Y>ַnak\V Mr 57%tCV,BiSZr?.R"- 0 gtx ׽-Wl/A+168p.׿/~+ak 60>,b/ -b g8fq;X$Vulcx5|(1Kb79; d#V^Se('VL!ØfF0L/'ybVs;Wpf)w9ps ];UtFזU+if93 hMY5.3g!L2N;d2p;Y>Ӽ`uWuU^cUyzWδшevokymL* ɸ[j;SFmUrYEw *@\"ZQZ.ۖ@*U߱U7YpCMG XqV\6%VWdxƯ[kG2 Apkˉ9^:<5EDhs~B9yt6ݖEwh5Z?}y=c]_g؍zc7po{.xc"d= RCҒ'd( IKN>Mv2qy@uGOқOWs]% z-BrcC1 D, u|(d` "\@ x?.0kr ]W | y}z@/aXW}7}GDA]Q H\ \R |7W qZZH HD0aXw}88'!h>Sz'zAK &KElJ؃@lhBH yp KvNC]V?؆q@ɰp y` PFS@PF756iІi8BHKHp  z|*add&S/ZhK%P 8VK@~ׅSXͶ HE*]'P5&1iRK0ȇHHփ8oɀ`QH}BWq -{)Prz؏t{p I%{)|=yw-OMw}C' xZ0~>,،D~4y>b9dYfyhlpw,a ^$H\Ė@ 1a~F,P{EmEG٘9Y#ta G Xi{5E}W X9G@/II~% G0){A} IɇKɔJy(8oW AؒpIy{I  9pɔsIvSQqoX&Yj7E}hϩ@Q|i6x]99 IWȈZI93ZH8Ѣ;AgsyE;Q{7~hTh}Hi,jIh]JKRJKZtZBzgt\apfe<@ Ť MP8 jH(>I5 \&y+:@zG;T1w|Z8o' #YYQTG )oC ᫗jvxa8I 'rhWɓZ?IZ!;i(7ZFIMh皤ңǮ:Z/%xG o ǯDdX*ETKhGm [DؖK +D7tx g۱ 2:0$[$ &*,.0+2[6{8k4<۳>+;B;BD{H IL۲F۴POT;S[X W^[]ba;fe{jif۶n+i qs[wpL PN P p~}Q۴R+k{(0&t [ ]0%[{#w 0 v@k[;*;6˶#  M  kRP][:~к;[>{˳껾8۾˷${ wPw `)Y0|4k{ :,&:`#۫K+ #+ [:p\` w۽$+#;T @̵CܸE B|,J;ۿK\`+9U ::ŬU ;šUW) b +5m\\%‰#P Ơ N@@*vZMLȈ+DɔlɒLL$ v&0<##+l$k``2 zƧ\Up췀u0[\Q0 5 Qp< L< ,L{,ϑ\|<[c.Kee^a[om.uwsy.}nY烾ćċN>ɓN^d[>{ۚnXL](zNC|@ z{3X1>CCPpp >/4U,[ v/dٗԞھ7tuGw~usg5wt2tnPtT%{ gpPWIwv'Ngmw zju _n|ПqOoPqU!nm,?gG\#PS6jtafGjdDjBcGffiCMckcJB_[/f\S_T_gjon^ihOlx?piNmar&vVguf>j{ut?f?oj@fP?j/Qj4 +%Oq5T7wm5UODgG l M0{oonXOnR?su?_moMkgsOmYI@Da$ >QD2QF1vRŏ#M4YJ$[QeL/gyfN$ TPEETRM>UTU^ŚUV]~VXe͞EVZmݾW\u.@b1X`… FXq@|DCldʕ-_ƜYfΔ(ZhҥMF:m 5yZlڵmƝm15\pōXȝ?]tL+Ouݽ^s޽zG^zݿ_|ǟ_~0@$@D0AdN#'(%0CpiN BRC \d  ʂy(zѰGJDX Z1EQFmQ{ށ&낥D+K! H,lpOL1c,eƟ-y SƎZ fBIBEXL"EʌdERHh *@+RhOt CUt FrT}8H):*VQ#)FePHJSO#I[sՂ X&I X*J*J o-%7A+ \n#Sjuv %i}7mV}ʖ x f#B(#}Z WT)\ BV3tbBc:! hP ~ue TgHLذcy #:JY$=RJ?8"<(_Vh RkNH *zVHh)A-Z{ZzJR W~&yx LRw̟>/^aF8l՝ HZV~2`.^WxSw$]}?=ix6Le΋7=T#-tVP3(%nׁ_=}VT3~ #_|xɏ$ܗդz_?)z~2_$QH\<: Bpn`DdAR0"%ZBQ2/AY\d$I0n:P]+ "7TI,q MB%⚜ As:ͅf8gDcոF6эoc8G:юwcG>яj88HB4|H@C6ґTZ"AҒd[DfғW:ɐQҔDeSJJҕ@Jy FҖe \җf09LbӘDf2Lf6әτf4is4#YHtPr Y5hc'Nun㬋CrMгu!0f6")LJZ /EA;PPR3g8ps<':iObĢhHҴ*($H)ESVDTᏈ#h"R$ $zsQ5wGiTԓ>j\Υ(رhMUSdDm^7;T~!iIJGxg}\$kկ֪gOSZi6Cа =HVuoc!ԟm7φVlhGKҪ]\ZЎml׺.@+\`6-rI6 h$8ýmn [ƅmr VV*^*8G#W)[:kx޷vٷ(fo^#dR9]cЫ0E' l)2/{@J  Eg1zmjuTZ2{KX٤mR#p% @2T?ЉQ@C7љm0%hJW,6ϤM\N2up+Pԝt:#0Ԩ̫9mjYֵnZjZ׿6Ux-`Y@^c7φv=mjWv7tv$=nrFwսrw ow7o~w^/n%o;/q{݆!VqoK|,O_ϼ5yq'v Q e -p1(w?~[ -n;n(j! sKC<$1hCJto!dvuurNvp[}}E^w}~Gv cEdno|ԧ7~|ōys~&(b 2}ޮGHo/#v|2~^{˧~;w>a0wg_y2Dۢ7oX`wޅ.S?Bsrӿn? @4K7YnU?oÿo[7h{@t?@ T n@A0(A>U: |6?4Al7T6T ܿ!~{/@7.ޫ|@>1 o+‰57w[A6> &|(d)A˹xehdnd0u[Bo0p@GAED$GD",Bw# dB{kEoknDOO6XQRSDp[VlE77E&7J$_ F`F(c*|l~Bo. n4B7|6$G{;7hkLtc(7 DC4D[HB\̺7Ls7Q~$_lGHӣ0@PPDQqAtDwHɛȊ(F`=+X˥47j j1́o#LL\L|K pHp!|NФoJmF4F0Bqt?4-,N̻%OdP j(P -мPP]MM}u?ͼ,6tA?d=LQQ7@;ɼr UOo!unTx6o{Y-Y%٘HG+ڣY TZ%}ILZ:H,"c>>FƸ׸q4؀+ؼ:.ZX70۱ҐmZzNJ;X%X[t98DE8%K"J}Cŷu\@l\EGm]ו7%]\\ۥ\܅\ݍTխ\]7^%8]7[[5X%G\8^][^};m7^s0Eݶ}u_e5_ph@Vc6_mchv_(_`gbݜ1`!   ``%`Xajaxaʘa.( ^>$n&">Iҋ@S)6&&+~&+-V&-/0c33F55f7'8p9:c6Pnl Z&8FNgXgHngN~IFJx)0H`p1!k|x1-8謹SH nސN h shg~gn*g@cvR.FVNh`8&j^jPu~tsjrlƈAPi:NJ/i~~-xfjfkn6F ZBjF"l>Nl*lkkl~¾vHPfŎll6l^mlvm k:mllmmm.mN.nzَmVfnlFmvܦFmӊlnnnڞvr광..n>nkfk-h蠎kmfel델[.Z>qYNqX^qWnUƈjLiv~6 i6+vq:,d0e#e(w )Ge+d,d-\jiz>i:΂iFi· ,sh#: p@jpf.dDdEdFgdGOdH7dId!rzƲ?+Z`va!>d·sg~s?w2(J?dX/d[G2\u]u^'_dWnkWu`qfugu>^'Gy3Xw`~xH bG[2b`b0x[jG%@(xS@ $S)7$bQ@ G#狃'X SB+z,v2,!`wrR'Ae.670ڐ 8?c(1_11v1A`Q{9SHX:gz`1&*ry| )w|Ј7|z"3uBvorj♣1HB//n ŁƙAaN8F+v/3sR+'s ) J/{ +:    :I@,h „ 2l!Ĉ'Rh"ƌ7r#Ȑ"G,i*WFb9peJ.[80_<0Yy( X { ~M]8i*֬Zr+ذbǒ-{Uم8Ӳm-ܸrҭk.޼z/.l0Ċ3n1Ȓ'Sl2̚7s3ТG\HXn5زg;l'ܺw;mܿ/n8S9ҧV:ڷsg"YǓ//V׳oCïo>?? 8 h * `J8!n=x`j!#]h`!8" }_)wJB'"x#_$yu,G% 00PTAM2P<8P ,.bP BHFrOSMy^8ZX2wpfA #, 2T {DTPA\ǖ$K %e #.蠑@Z]1餐Ꙭ~5ZU Z?@ C-rQ;6I(yLP[ЧǖJgDAކ+9 14g뎷$PHPO-z%;пy, hj/ۯ\2TӷI$Jᨀ)KY -%*kJ$UZp 1n)J<u+[n֍g7[Ѳ~ի@ʴfq:=({A2h @] *10:0NAdwTQ֦f7 ֝FA{zw%xw %s^WwW}zw/9 W yL`-\aS~!_ 8Np/|xA ]<{ǘ3pc$#Yf2L?0nG;Sxܶ6}i,)G2˫[1'F˹ :@fv=E xXf!X)*ù:@CYI[ғ4W1UMcVղê0Rd-W)֧0(,xXWQկknˈ h;YaT5T UeO`Ѱ"UD5qY,;*E;tXgXajj;6Kz\WNH-m\bwCP'58g~".4g§x¦Yl*}[׹ˎe7k]l4o?.L(~;DBr <>/30j6/%G21Aҳ }\/w% Ȁ2@$vsz/?>3~_̴Sֿ>}d>_??ɏ?o??~ӿfz~ A .1f>F. V^ n`u_D Dz   ` _2 `BD C 1 Z!!n>!J!RE_naz_ a !ED"b""*2:bBJb" (CJ(HM|bֈbլD(>*)"A**+j,@bN(--b/F.D0b-"00J/3:#F2/"#4V#36Jc3v#8b,"51~9^#:&8rc:.c9'#;;֣:c6#/>2N?£6b#Ac#FFdCJ$^2%JQ>aR2R aSSVbQTBw ]bT`WW`XX`YY`Z`Uj_D,84 \%e& `2!:&J[RZbb&iLLfrAfffh j&Qf|kff&m `nVmbo *ffo&n'p~ r.'q&='tFt2@ tfP$h'x^^xg%Muc {'0|'}gtgDg'u'` hd 6~2D.(>U8A^(Ugja~((((h_feȨeШE=f((D,-Aɑ<4=[hOPLI$TD pɑJÒ,WL)L $J4[nJ8&.,.LpδHJDKVJ\ј檄"|JnJkѬJ梮J,ͻʐLˤDQ/LDElfJ+ NC\;N@MB Nz/>pǖoRoo$/B/FA\@ɚl+PD :LÔ2tjgd0DܚN$CF0 \N"@d,lpx0\@pdo@0p[T|`, Gۺppj* {N |k$ +s0[E߅Jt"g(,DjAЌ…0HJ3C@qS1\,Dȇ R򥶅,r#D2W[(2n2#qJrADPxq6K'GBTL(R.OF/K/ߨ031132'2/3373i<4ι ڬ4gTv6 &f`s`3`387+7sHsMM`3M~`K`3Ld;Gޘ?2ek%T >[W|2'Zy\t?!DuR? %Z(Ptu PT;\E`P<P4(`_Nv'xa e8E+%! Cn$ ]=%fg*pKl4>N* "f\*ɮX.RC$S"',(շB=8,r[Ǎ]@VC <;dG*3=lF,r4THRԁ'JQzi2$"3J` ,'H`\>B%.K^v2 $i9`]LtTAɘ+I6pBP s f]nmw޽myk뮗]o ׽3 7o[ؿ0|wC n C&%\_ oùU@A]v%6Qb;6acϘ522d<& d!E6򑑜d%/Mve)OU򕱜e-o]6e6ќfmvsf9ϙٍs3Y}3wgA9Ѓ64 }hEyэNsYhI9ѓ4x+}iMyӝƴAP:ӣvtMhT;l>b47j\ Z׻s}g`&6-i2â(o hfBv/#N6! h1o7릴nx{ކw|o|7Χ(g f8wpmP Bp_{%k=@{ ra<'OϹ%X"<gmhAZPMF uW.砧|'o:ɟ.򨃜yL%u`<ɻӜ6ǹ>>ˁ{@71 8aSX{٥=7~/~O[G=^į~G>3|>Ȝg[w>>Y>-%. nN$ov+!wÎ!P.,* M.nAr 0NmZ,l8 M WvPz N N lP z pP0n! Knڌ.- FQ'p+q26.q:C(1SSQ?1 UqQqM=ˋS MMIay1ԂԆG1oӚ1ה5M/--̴q1q,1qѽ 1qq1q+fM1 r - 2!r!R2.2"%r"ܒ(2#5r#82$Er$I$M$Q2%Ur%Y%]%a2&er&i&m&q2'ur'yrN!B~2({rXv(r4J1J'A2)Eڬ 2)٠R*2F+ 2Ȓ<jk2,n(+Ba4JAV2`rL21.@ T+ k)11Ca2D$c81|3S4#s22R20345˒5-k-E"ps6 B! Ls2ACR5_5  ^3 1:}3 Q.!CFS2l1rn2842235##<#a<#>SlsDstS?3s2&9U+2:԰.;#!7CHSDS$>!`BKB{3N<@<5t>S4>W>L-ɬ63$KB*=!#C!"@U+0FA;CtB+=a6 hKD 344t7k0,2/$4PJtT80?,AO*4tR9C~?s?!+ Z@PcR'SΦ3SUJP!ZB##R3  #R# V+tfu2l5>qu>K5 sTaiSk4"18a2JP5YC5"a!U?+)U͕Vba/!2+#*+]#]s413v`15F5aoaݒad!8'cNG#`5t\=6ƆR(cIdMdQ6eUveYe]ea6fevfifI !lg}g6hȅvhhh6iviii6jvjjj6kvkkk6lvlɶll6mvmٶmm6nvnnn6ovooo7pwp p p7qwqqw!rK47s=cásIPa @JwusY7v!dv{Fuaw}&!` T״!,''()*++,++('''''&####$%&) ) * , .145555551-,,.2489:;=?#B'G'I'K'K'L'M'M'M(M(M(M'L'K'H(F(F(F(E(E(E(F(F(E(E(E"+>(.4-00.05/1?/4F.3J.5O-6P,6R-7T28[9:b;TkKmFGbLE[PDXTGY^L]jUbr^cvddtk[opMks@@AA@@??????@@@@CH͹RʬXcks{~•̵ܺôŸǽ H*\ȰÇ#JHŋ3jȱǏ C!ȓ(S\ɲ˗0cʜI͛!MQN@ JѣH*]*dLիXjʵP RuXr׎bϪ]˶۷p,+,AvͻlN| LÈW,l]p˘3kS:fH7մ J̺װcc| ^#WnZi3[ȓ+,yjܷIO:^ҵ N\t5?M(Bzk4'錑'hFU\ ig'AA 2h[ꇡqHp!:xB0~`CS4eoը8bqɢ#6Pz1F)Tj{C6ye!ZeaSꁘ} GUP ezU|ہ_>YWzeIB`ѕC祘fN32'nhhV2[X>jj뭸RD뇺Zɧ㪰b祬Qg殹6~ht+EvUwhMޖkG˜k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L WS )&C00Cb!(B I@B)biH3  rElQ XTQ4` b @, ]T JD@ H0bȊ8U\C9/P 'B!i5q k#%(B1t@`$]b(8J4- ,2X4( 9b)H–  yO@ 9@ Mi ^QK$\ @NRSEaDjij"eVZq 0!003P!9g&Bm 4+ !AljʎS(s@C4CFĝ9Xh P FX+ؘL |S< Ha@CELTEUQMMnSvx4`7dpV`u\jVU|bkYR)Q~2Gb 6!VE´i udg [MBf P`s%ը8MsDq/&"Z0/HJEavV"C Kbm fVz֥ jM+Z nQ(lAksMA]/q-v H Ajsrv? 0!ַ _֮Alۂ݃o/Z}LdnoQctn7޻l1:7K]V}p%, ϹƳg^uK}}fKhWN>O[Ͼ7otFыd/7?Z`8Xxh=C ؀ 8Xx؁ $X&#x**,0184X.X887<;؃@?F؁ExJhINMR8Q8NXVQHAxЅ^؃X84ЀR` ۰ f8e98tXvx:S|ðg P  Ѐ(' qȈ8H H  p x@Q@ 8U @h؀ XKh،$8"h@8r  (@ ȉ0 b耣00 Ȏȁxr 8( x C 8|ȁk0H 討X 0VP Ȁ8ə9I8ɀDI4*ʟ7j#j򈙠 <DI d H:Jڤ2ʤʚV:SpCZP$dw$I: ڒHʓ x C`IPH eyꀤC y!)i&بȫȦ (DYpڑکCPpXYKٓ I h ɎHp m (Іozڌ%99 Qy] kUKpPh K0dj? ACKEKGȅIۀMȴM˯QCx˩SWkY]˄_ka ceUKgk˶mo[q+u;^ٶ=+ukqڷ%ٷ+;w 5 POp ׸Ѓ bL ;='Bc"b= Sؠ w3CM{t S 3)zۻ;QhfțʻۼΫ;[k؛˼ѻ۽;仼[蛾싾۾˽᫿;ۿ|<| q5$fcE ;$<&|ߛ*,0a XGz3;=2BܼD\{La `N({\_ac^g5Em elisuwyZ$~ ej[X LtaLtaEXEL# `ɓ<]ɚɜ,Vlɋ Qɡɧ|Wʁʫɩlʯ2ʰ\˸,ʡ\ʣ<ˤʴ˝,̿ ʼʻˬl ͜Ͳ| E|ͽ\|<\Ϝltu<|m ɏu\ٌΥ h -\|q-؜ڛ]٦M ĬKk(L( 2% V'ģ ܾ2ܳ~;E}ݺӹ~ݺ*&S'O&}D&S0ذݱVFmtFfmFmD{UhQgwBo DoFqdh.ggm !h1fBFfkfgK4GutGv[CMKiFvksTjAoQv9>;^IpvoHPFI"3GLNtavqVmxi!upuh[VnK^?-sVQvp'o%p p7B PVo|p;OB%PW(P^.o7G-IyVVraDW;F=W_]A1w4s6grNT9`T+E?2!C#<5r]˞rͮTsނs5=$WW(WW/gQ ^ZavqZd>>ZownF1x yv- mx^!]A]Ty]x#foTWxx^xV[ޕyW.o] #%^ N2 C``{~lFDUAy^NV'~LyD^G7ab-L`joaaA<|~b:&Y׺_:oWO:_dCC]o_7]b0z0}܀rֽFw6OJQk/i|> ~.pG~aG˿l|G Oolyw?Bͧ\vw–B!z6w B^HVvK(К5E2XPB >QC VŌ 7D%MlU+~L™mrRN=}TPEETR4e1cΜK65MVƚ3'B]6p؆r)AQ^V%Iu*T|8o~*E?Ydʕ-W~1qbQ=_xVYҥM7w1xipaX[NjCp/ PIR%OXg5#ŽN7iˆ#¹.ٵs};{]zݿ氥"[6'2CAXFRrd5*"%ۯD+=Q/c3Ro1GwѧpZ.5f)G+"+무iҬ'r6Q(.z.GHϖQVhF$Ei DL9N;) 27s#V#=^5%_l;@)@WPWzk;lֆ.́zWzmv[@Ǭ;o;;p'<' gqxm}x7xg柇>v壧h߾{s|e/_^|H_?8>0z(5Rwm$pu\xiؙB(-^ H]ױ}Or׭<tcތsn)RӴiM7L{:t -4 gRTJUe: Z(:!Y.bG'e'b `=)&N /o eW;||+ ;_'<h;y"aXzwu)8?GMIM1e'!ߝZ˦6Pq!a@>4 l%?pA'\(DٙB*,!+4,ع|$.&$C}:a*&LC1C2|C.C,C*C(C'CC[*lC+Ĩ;ħKD[D? DA|D&\B@DDKDD5DMDJ' ,:*SDTTUdVtťVX(hxy\]`1@3E^$b4FiTF8ftg4(F_llFwnpFh4:XeG1^yG]G]88j`Ȃ4ȃPu<tȇHXH ȋȌȍȎȏɐɑ$ɒ4ɓDɔprtɗɘɖɚɛ|IɝIɞʠtɟʢ4Jc9ʤTʦXʦK˿LL$DLTIJ8;ЀXh]Ѐ_!IQs 6X6(MQX$Q8Hb͊լLD _DUPtK\KTNlΨdJKڐ 'XXXT8۴847HPQpQpO`OΠd |JL=PKmPPЊ (AϧSAGCUBKSEmTNTFTGGOe 8LCD]VTWI=UK]UMS[=P%UQS`^@LUPU|d\USUdeViA}Vb}UgUhb-URQo5־` ̘P l=R/=Q1VjeS\ dUX Uo WreXG5 \X=RX$SXX XX Յ ̨ R|5Q6R[ 2ٜ,8 ^~Pn-iT^Vbh NdZAZX e@ ^e p.>sde6 hgA6MegP̲fvw}Lgu. 4fAFQmL+ى*,AgLVX X蕶=d(A f=hNhNIKqLO!MHfuXwffeejYF5f]VIh(!)y`gbph\^T&hX8g)*-- fh+yf9VTh.hPx\pQh,)Yuu5YҸ<xQPX8kX r17p@-'نsirp Bd N&0`M&ja`/fMQ'&sFZXuL1v/Q`6pa帾& $oho @0Nn3zn(oq_tgGYo,'7GWgwlņ ߜx6g5eFwyy"[! 7WgwzOz'G{gwwg{'{zzz'WzGgwȗW {?mXkh k@|GWow}G٧}Oz9(w_Z8(_`zPGGMM'(!5.{A2'?}۔Y nY`aQQ; S* cR@VU6!U^!~xa0(! @)!x.8ۍ=>7h9PHxF1y%4򈥒ZZP]: $" &)g;~ifey2dhF!ua'aG{yMx(q(.TAQ s0" /|WAM䨡hU d}5y9,[2ϚrUFh`H]bhCdԳc4nd  y]˟jk׿\[T!0f q`LJvdM05L^?% ,C ndkl,ss396Iq PpoDmY #KԴEVf&L2KHU*@0ˆeC<4jAb#c=p67~M-ԃ#k8'8FG{KmyNk.8zu~瞇.QKΜ.iQD /2~F;4|_+}ԋ>髿>>????is(< 2| ?]Eנ58 r C(&$ovTpC`N(Ұ6!s"0J+PA`F<"%2NdWB".|"-r^yڸ"Ө5n|cXAp#=r kL<$"E.q 7IR$&3Mr$(C)Q<%*SU|%,c)K*-]򲗾%0)aR<&2Lds|&4hRԜ5Min%6)nsf9ϩNi~O\'wy|g1*[s,AՉЄn|`E-E2r B/͌j'G?͐%'IO ͔6Pa(hQeKut9{ a u,Qԍ. -Vഩ U:Rn&5VM)Hlpl%RWΕu]W}]_s 3ר5dAc *豷dU٪X֖fÑ\Vh?Ў P-kO [zִEiKZۮ-pyۺ6mmmV}s\RnF׹ٽnwKņWx{bMpK^ХzYou_۽o`3x d#[Jll] /m!6e3~6"QAFD־6}DB6-qP$7ӭuׯ5N3HyƆw3y{b77 U0QP ׻ ^SܒA.^s8!D&o2.9c.Ӽ69s>9Ѓ.F?:ȋ9؂Kot'=`59&Mb!X؇(HMVW;!;UPf1;vF=.~?{ک(XU3q;nG>'|x$[WzcD髎? آ0죁XXM=rQ}0!!#ʍ149x*k yOxpw0P@'U>[T ? o?_U_ 鑟9/؂XU V vI Uh@&T(ُ0U,P,t U(P( EaI}pixB_aV: HaUPa^U\!UdaYR` !aR-́an`U` O aҡMեQ,1< &b\ $@_Ub p ^Uȡ&'zF"%B/U&M'F-"V""J*.b3b,b 4c!%U z4!&b 9"[)R\-qbR&#)ͣ.Ԓ,#?*=*@>>>P-a,@RAUU d%'-c3:AAZFj$Gz=dF$I:-2dB.-X5=<0d-.DJFR l(#!VdUl ZcU|&|bNa؂-neWc uUؤeZZW:U%%aUiܥ"eXF\~aYz VW d0HZځYP˽A50UhU) ʌb$UH/Pai&Uf k>aa,fflanaj`o&*"&m3"i*n2o%Yuc7^vz Z&dOQE;_mgjA8ns~-#MOz݆%mhUؔc5ݱ!*m&m>$V6&vY$c&_Rł2B~haUf( dzXM0gށ!ZRS'xhr EݕjaxV~)))))i֩[jPѝ.[R+lRjQ4PУPj*؊Xicِrꦆ*Xh]d*XRbj *Q檮jR$*++&.+6>+FN+cY+aim̹ʭ\EQAƹ4]dΑOӺkV%:9r+>6c"u |V٨'H+V뾆Р6H%Y,9RaŦkɚ,FȺ,r,\ai` BvƬJВPƵQKmrҬ͆Cl֪٦m6T~2D"-bJ N֮$yƢ-~$KjN.-n.znr^$mݎ,13B#NT$T”*\ת.-jײQ&U(o65Eo./j=V~V>o /"LJDdM (NfB6eBFzJooofv-؂,ߎmm_mޖ,V>p"kRp_Bx2FpT00`~ދ-kp0 p񹪬-κ?pbU|f }~qv-Ւ,ms>3??3@@4A`AX t08CCHtpPtE#4P(4,TN4GI4xtX04l5*̱ /¤ ,\JBB$,JU=˿Jo Aj5gzx?JgsFyD0B{ B羡{͚~~AB7﫾9{~G۾Lb[#DOKW:t_|\i5$@,8LJ4@5d-<!Q j8bE1& D 8^C†TDӥ/aƄq_EH Qr Ut)iS4 OC,8aѣ)Wi,ٍ@gЭL*viY0J)ŸD%2T|24/DTREPd UsgNi"9$N0&G}9Pm۷qfM U۶BHhfTlbƍ&# /U>F<Ѝ |0ԭ#D%$=ot]X˛?.}s 8bRnoS=,b|¯A0=l @_"OPB1L"aDHF#i "&39I9aCfxYr `I)1m# #_+5-j7<3M8$MT7L(=AtB Dm$L3etHÜR'-IM L=P?ԲS4_]XQR[Wt5N_KU{=v3eeTg@i4aVmo Wq-sMWumwWy{W}X .NXwڢh%Vx1X9AYdh@dGnaYifMF@矁Z衉.hw繦Rhl+Zꩩ檱ꬹ^.DdwlFǝ gnf;o ;q6|WGa]_@Xdy߁^/O^o硏^驯^z9/F____ߟ˯Z`.| !FX`1H nЂ> #$aMxBna1yB*` m@;!|CQ," 4C]C0X5RH -Ȫy" H9$%'4u΅L%~ydrl&(IA6K5)Z,)KҖ*!Jx?KVs"8|J Znø(@m#Єmi鸂VΟChmQӠ h 2 R.ԡ}h&O(K=R~4v+)EMRmiC}ӟ bO0 xSjT@yXHbW[SJoS W+g-i#j)ɾwNrzl_KqZ'Ωma~q[ÆTGÒimlqE.|ZO /K`6 ^7ʷ}I 'MݔbuzyMh߶]W#i k8}a.N<5p*l _L#pQp1 5䰍7\XKN1 JT=-o]f1{sȜf5g %g9ϙ[Yg=9`5hA* hE/ѸiIO:cD(iMozi8jQԥ6QjUխvakYϚֵqk]lQ_[A@ Ln@ `A" 1#.!n$lcX)7l W.,6p%}g.h# 'm u*P(dF.Afp Ap+cqs) /v2$#+ծ66^O0H&E C!6voL }^2/rͬrf)50a$3-- 3jԉɤKRb&Hʩb05լ|5&Lmo:bk-"Jo“cZw*6nmCD6:/Q;/ ݒ8?@O`.-'-g-9Ȉ )W""(=O RAl5i2BeD []F5P2P2QGrIC zg3o D(%D)!13LiTlTf$q$ 2+Ir`q#L.FsA3 ˔Th5h5P5QuQQQ!5R%uR)R}'j#,uS9S=SA5TEuTITMTQ5UUuUYU]Ua5VeuViVmVq5WuuWyW}W5XuXXX5YuYYY5ZuZZZ5[u[[[cb \ѵzb]]]^uy5"^y,_6yBa` x5`Vv!@c! ,222222222222222222222222222222222222222222<4b:ADEBA?????@@@C\zӗѫжϭϑ_TROPUwǭ–Őyk]QIC>>=<@>|AzBwEs|FnvDjt=hr8eo8_j:[f;V`:PY6JS7HP8FN8EN=ENAEKHEGOIETKDXHA]@?a=>d<P4J+Ѣ.mdL۸s-[aVugC| A]+/8سk_]xݟ<~gߋksg8}(`J]FdP]u)FqwpUFqHA= VV(,Z}rHPy%_I=uc2jk?NaquD>TVief=aFcwߵwjݕF8\9z͸ߕtilq Y[~S%jڇ3WF*II$Ef#бgt!HDRjꩨZWn^i8j뭜v*+Z™T&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:/KHS0̠;Ah D&jHhB'DdJ>ZTђc_^N?)FcR)z|f/S†щA&6XIusaIrL'\v x} ?)Ђ$(BЅ*hB%Jъ(E1ю2 iBO4)JWzOt)Lg 6Jez2dA"OҜ;w&0 <*Q*UR*V7Ujg)60` \A*θ':Pd LZyڕ#~֔BY2s]vaX" r&՝P+:_}Ac+ۂҶ-nۢ VɆ,t܅Syr;;]邵e(,0P vK]nsI]WD(+*aݮ3 g`)0yva [ ]6BmD{zvumo;VpF,zR$q˰_W\#(+P`' egv~"&bW֭gSºP2\+< t[,&ͺ5Mb1-]/ԛ m-ĭ[kMVm~L d8=v~xz6AݗB5(B؍}>Xৢޓ< DRrH9#I@iԇȌ.đ8.GF.C4%G8w At^D~.nj_\ޮe q8LɢތN Jڥ=BAY6?/O_ oɪߎxKkK MAɘ,8XE*)(~),_)iYC!Aʑx0>FNLGa֮,XU$|^fh9vllnr?t_vxsz~|_?__?oTToowo?L0m?NEoo`bu~\0 W|tW_t9k x:/p7}|BN #:pQ$9)ܔ |$~RH 0BJ*&,aH1jN=#A%*d"Ț fT! A4Qvh0aq٪1AIp BNJF8q=TXf#kرem>E <Z&2m5Z"5$/ 1xKsMbYI . Dy? >\`Z?Tv}Tcڶq+,>ᇳS#"pOۭ7o1 Ap" XAdD'D6 d/;"譻 ً.Sg0oD,0*)h*0C DG-TK  ֊{JO!F6NK;֪R$Bڲ+Hȱʤ4ēM1rN>W, L00$HN=-.<QZ+GC;'P <T#$F"yDW֒l5ןS e<]R2I@I-V B#WPC$P8h5SHB} '8*S];#j?U*MRa9xj#ܨwWjl^JE8fn]-UhWM$SS1B.VZq͓cNyHMYf?R!ZQluJ%5F\B%[(;mi}Jw0j[mGo^;O78 ;ƽ\!'rԛu PqY'vQ/F_#Z!D#2YT[U9Ɩݚ$Ulޗ}?Ǔ{#7}}gf!^in[H? ga/!,+a$u]ןV"j#\V,sS2P-s*O_ъJPKZnOJ4ZLDʝ^, (CQ9$@"AX2Bs3[@./{*d2F a Ds!ra=|r0ىnF$qȼ(kz֞5sf5Pc3vO]֫U{`#35,].$uH Y5%d1d,hC{ђ=U,{Ԣ}-kcҶ-H4.np;\׸Enr\6׹υnt;]V׺nv]v׻ox;^׼Eozջ^иýo|@x ^׿p<`Eph, N}l` #wM_WqE2c*Bh33P`$qB:gAPb'>S vҨ'>c T<q7yΩ+l!IĠψ C+ &8u#垱-< \ Єʥ u|coOՠtdPYMEC D7z"gÃ&n2cy85oo!>ikL}kXa{i4B5xB?; ?gno gFZtCǾ}BSo{w j@S克A{m½]mxk\_0C0ߓ@S@;9ѐ;;`3+O|PKjx@`0A[8+=f 1+9ϸ3Pҫ@Ǜ:S9;㲅%STj(,; >T0]@H`$2T H52SS-C t̄L4N<׼LӌNߜNN\1O\$OOԬ|NlN \lMͼ*zԲ&kɬ IEPIIHHHWSIw9wp'O2HH]HEH-HP Eżl C8p/ PEJFQPQE6Q Q}!RH%G&G'G(G)]G*"#E-rRqRp on,+Q,#=q-e7mlkS~LӾJ8R9>T>TB5CEDUEeTr/b@]kIT-P,S; 8I=&(WU[9NMC:t:#0WWcE3һ;V,dlM{$yӸFh%'04mevc[ `k?w?I2Vw%؂ . :sź٣05؉f:`Z- ,(•;$GP9>s/I]׊eٖM52 $U ~Tuٟڠڡ%ڢ5ڣEڤUڥeڦuڧڨک%1jD/T@ڮ-Eڰ2cC1#Tֱڷ"S6[;5%25Ŵk[K[25045/ C%ȅF4[[Z\ 6%$M닳:]>VC0bpҥ]Դ=\M\\N2[KPII;XE]U- JPIG޶R\e*\$U^E2ڴumGT+5H%<5 `a2PQV8;4k8ۀË#e)u7ИY= a6FVfv闆阖Lו U[erbp6襵EYjjy 52qMܵ0Sɮ6kuk Ҥj곆ksnj.bMlۺܼ j߹6vdžȖɞ̦&l&m6mʀ(Ȁ$f&ئ&Ӷbڀ(A)X$QEmbmȀ)b) i8 B؁jm&Iu6Vϐ 4ՖnNCPn0CVfAЀV)n@poo؁CPo>opp]W@)nmNF((VoRZ`qqАqp̆&mr$W$q/n_WBnf6/-- -sfuXX;W&jAhoA>)Hm?_)8rs-otCsE(D?/&tBn@AtFtXIG't-'?7oDoc}s~r7а3 4ou`\qERpuqՖ4r,an ^6$m"3wlA`pJm6g?ܞW߆0 Eog|/) $y2syJv{x%ImtoD/C'@G;gyv[ߗ X\jslVy8X΅ajm^1ySj]VyTu+/1şv+?zxZN {;{561߹Scl=0|Kckױv[y]E{弫{0iq P(pzˏة7\fjq7%ȅυz0yj}~n'7GWgw~Tju]~~V Ls*GaWu(UCtuK]tk4H!P„ 2l!Ĉ'Rh"ƌ7r#Ȑ"G,i$ʔ*Wl%̘2gҬi&Μ:w'РB-j(ҤJ2m)ԨRRj*֬Zr+ذbǒ-kHwjײ]-ܸrҭk.^oo߽.WĊ#^aȒ'mLd˘7+w=.͗uQn `nMv۸KoQR 'Jr瓣K^6닳kg5ËkqX8jwgI&O}y&{8݃Fh݄Xl:vaN!D5%'z7-"-a58[6"=hiY1k%N.K!2Gq%G S%Yni%ZUBZkG̘nk*cu&jy's9x":h9y~颓hbxzRJ1J 諊*Tl*j쯳++:-e~mr&n*ނyev mV;d{$d'pCipa} +id.(|xqw k'u$Wg2v!qɥ0Hs4go83n']Bf4x@mJt`P'yTEjVu\ \L&|`eh[5m]_ۯl[nkw{߭V8₷;8K>9K[8sy斋>V${"@':IBp :H3T*zBdq1P{eŋY(| , E;BPl |;+`~Dasǐ%H@Xc ,kAxYjlbwT c p$3/"jBBAP|@ +0!B r3x *T BC&% =T}#GLH)R!Ud-ڢGqXFX e\ gXCDzH0* W\/PzD&2LR)hBByC(a &r?1!$e T&D-`BZWGI!(b e,}i'RDE Ӎ i^2'YɤfnZ1-Q>5BDaBr!_B)Ӟc 1^ B Ȅ$vV!;+x)$'*„YcN\8 1%Jar8bFkz?U//!U(._9u")SV^;Q%wrs;.ܠ$xA Ef๨fz,Zr<6gF  yGtCBxFC ZJZ"ߡR Z#d[<[ԗFiyGEKʶr%9υ:&ˮs-a+&6e3ƾu-iSڎ{ms> q>H]et~ՍQN.}ڔ'4g 728S_S8R 8C/ TE򕳼!yB6ya+ M9Ѓ.tNr.;3ֹӣ.ST:ֳdk^zaӮnwyNs` o;ޓ~ Q_B@DSB!8k%$xdžq?>b8ArĢr{u,#KڨZ[+%.1ǨvYĹ\z9؍-TBYlE ]) n NlBRYُu  Ơ ֠   6!ZY~@IN\@8DZPE>cH~iFV8+jbDb3!6"L#2N$.FM4dMa>24>ޢO1:%Qa&;d-bN) eN"#?4QTDSZ0eF46D3).V"T\#a"8.#]Pc`!@e=& Y0?"&S*%acb&I"ScnJT&XWW&hj&kk&lƦlΦBm&nަ&oo&pp'qn'r&r.gr's>'tFgq:tVuV'u^vntrw~'pf'xw'yu'ztVp'nQ'}&|'~}ڦ|~B'~(}(.h:(fFwZ<.ܦ 0n.\\mAf)dt&&@TBnBQqBHpAl(ڨm~B((o&t@(hBhm*):in'¨zi.i4~h)`@n)~+i)p)fП)Ȣ)n6oL fLNzN蠖'oBHd(VDjZ+  * mgm+Atڦ )XA&' )'pmB#`#j&ipf&i+m:kJ+Z+o++ƫm^6|*쾺´B^m~ʫsd^Fj+l+&.X,,m6V:s"Dz,l-nBj©l}$+Hlnv mЊ)m>)o6m>'krb,Foo-ܚ'fhR+l) m"2.sB&fmBnpFf&͊k>&-辭߮nn~ni&,6Ӳ'pfZܦؒr,oj,f6mVBkt@f/Vodo"/*s&i*+.0m pnbooHV|.on&gt0op"f'ot .0O zmEDmHjl~݀(k1)f'1Bq1q¾.gEqź+{11q1ʾ/6& & mJ%r!1 (&f#O'/$%q%c$)S2.l=GD𗚲*K.2*1l2)wo2r*4?*o21H#ߦ*- p G35O3+Wr:ms ;s;S;9߳&'q3nt3.+<243:=-q",df#,0֭"nnzt(,'nfFfG-nsrnZ4Fkoִ4KJ.OO&P0N"D4J㦷ʩS,TRV8nҰ 7*YsTO:ED4C'0kn*&n34nI{`Ϭ$ئ$ /2v^5~B`'n6cBdV-ff6$#hS(hjs6ozvchCdSfuo6h2>uqFuG;7oA f8m9oY)'"kVjslE\hnhjH +jIcꣶp,6w:8B/}w}竔H~m߀+po|ҷ}O8Tx/7._K8~x_xoUwfn~mV`vwm23fw jцꨖn7YSfxCl&un.9MWqp' ~(+g ysywg{ysg~v{ygcK'':2:s.C:KzSy;ks:{:rZzzc:zzzۺ}&;j  S?Ya$e>KOY;w{ef(n% ^;}f"b[&{,sb"P0yJ_ITS >adF^EM-ܿ\û@$UxdUIv_鋤I1UbuV1+cSX=5|BӽgEF*ٔ#U\ƛ\OԘD?Y^-S+2C֏??ǿ?˳M$Nf?@8`A*@aC!FlPbE1:cG9F&?TJ/'”93!M4[1N{ 1P 5ZO 6Ө*ՊukWYX;͞V-ζ^پشA͋\q8`</M,vPÍBI]vǝYt2IfjՔKB]&[@޽zނ,.6aO. |W!#@9zvܯgNx>rz{+H_= PCMpS,[CQTQIm0H g(<GQ&Kk@)_2I&D̒3D26AܒNO $O0\*1HrG& Z4RI;Z4KE4M;t3O CTjxB&pTS5#P!1Y51[5TW9,QCRQ^#C6Ye+/g-iۢV-k!l6,ob"p"w+sYuOV#v#BW)yW1x7_}M)ws_ 1kB`fx7X`$ʸՍ='QQNYYnaYiqYy矁Z衉.裑NZ饙n:,駣꫱6A [.l+FH;,Bb&3 ftDFnF[vnnGZp!oٚ-B ­ǩD$&QYoag= fBJEjlrI PB )iBgwߙ>$ع/iTڶ""/^3 )!_]ջH_,W7lA c %l[P$@ , @`P ,h2]0HǺAf~t a  P'x#-Aj#aC@ (Cݰb@[EEPo'UAHe4&䈩& PjjdF؈C7qprImqGĎ#vCmp`AfH:6$61)~4)QJ`5o$p$I(Ilo!HVX ,3KZzQMKPv̒ӄ%qbHYLiNfdHw)L]SbfIbonD8-ΠkdA"&8CO(51 uCY'8l3$Y(:P$xMͨH$ѐxo(DFa`|%q[ڶpR4$%LSEe|IFMuSUNUUUn]WVe5YњVmuQux]W4&2_Qד5a:>. YQ&.-e1Y^-`}ulH" Zn)q-rAZծ<^g{ْ%MT4m[ݖI{z;%$@.mcNJe;A(ԹrNW.Bﮗs?kƷ}_`6`/ضKe!\?X p1}jpYئ dPH" 4-&q( ?xmm{Gg|bْ?ݭ;1aHw T3MOBM`,1]東2]R=Ry%9y} :C[]CfI\Ic莄dg-ŜA9LK2 D ͹Q!i#:'61` k2J|4 ׽uGkS RR+ F@ R gx$ƂE"" m6Hkot&AED]گk[6!o{t'A,PЅH@ 9m 3a so|elN1["ͺD!Ԑ:{Mrj(f6Odg9Qe;Gqz|hʻmbv^mݑPZ䀓gmNmXVK'n Rt$yNKQȽ$hߢu} ?j x/w!yO1yW׼lF~+l7X kw,}azhm?6'c/}tG{{ܗwh&?,^9rOβ^l>y[X~֝=;SlgW?GyͼrolbOOK$]; J˰$E*зu0؏f O/nclC0j0ivpJ$ e0'oo oF Q kJ p p ] _ 0 p ٰ 00\Pp1qP 1 q!1))q-15Q7=1ECqMKUQESqa_1igqqo1yvqqaQ17b vw qqI1qQGlpKa֑1Ar0Qm۱{Q` w :` P PF'0a+APD1!kUY%%agre& w '8R)u']'R(+#]r m0!  (4D?2$G%M$o'/ss00 31w -J*P,02q273)+a 2*Gײ2?23  S2߀SmSSs81311#')K:=5P;rza; :qF5369;SMTKHuJ3#NfOasO/)1>GPtPTs5mM T3>DaLWRTT\?]8QW-9 WXSX):.e3g47*W @uu61-r\]]e^ae[eUfRӦ@tF0oTg}vfѱfo6svg{TcRh &2̲Cum,2 3s`6[/U6e6&ǖl˖&VVm6'mVlV0ev;o8W1#vpqSRqq)qqo n#r5rOvr9Ws6s?ptMqtU׬PX *N0`^ WHTOvY/ vqװ` ,SO xsovK ]PzC"dO З 7 z/u yXzW C{wQ}R^k W ڞ *$2pF6Pk/P-#d_G=90(|>fϼ՗~[TPx}.* ܀ɋvaW w{ܖ}raϯhO狆ؽ֗}uv{0zXn8x鸎8xذ&$` 9y!9%y)-195y9=A9EyIMQ9UyY]a9eyimq9uyy}9y9y* PA ‚ u iƀUJR̹3ǝ陲`yBy!hl! ,22222222222222222222222222111111111111111121214283Ak4CV5DQ5CN6=H73<6/62-3/+0/$(.,** ) ) ((((''%$%'''''()*+--. / 1 2 4 556665555566679?'C'B'A'@'?'> &= &=!&=!&=!&> &B'D'E'E'E'F'F'F'G'G'F'F'F'F'F(F(F"(F%)F"(F!(F(F'G'J*L+M,N .O)4O,5P,5P.6R:7c;;c;;c<c%Bd(Ee-Gg6Ikfo8gp8js:luCrxGr|FvB|>;@BCBBA@@?>??????@@@@@@@@v̲͠ƾ[RRQH*\ȰÇ#JHŋ3jȱǏ Cbɓ(S\ɲ˗0cʜI͛8s,YR;u JѣH*]ʴS<FիXjʵׯ`A`TaӪ]˶۷pV7#ڈU{-Kpoݿ Lĩy5Lbѽ ]Ƙ3k̹M>/PI*- LA+{M۸ ɨC.؛dN~Er3t<سk|ߍ;Z2x흾z[g/zߞοd_wh?'ށ)X`h%g~ xWrX^Q6Qh(ZAgtJPoEWUd-zX|`lɦ xk-PFʕޙVӒqޗY&i XTZtR)t!Zbioݩwk҅sZ֩袌6jUMڥatg\NL 觠*K hnd9E:*PjΪ뮼#k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o @ 8: Z+z GH(LaU:.! a(6aA!@! 4"%2N|(R9"U(.zY'q~X7Q~@7/N';׸;G?o+`@ @rd$ulA̤&7NzXC(G9JL*WV4%,gIZڒ.wKX沗 &0)bӗL2WIe:3|4iZӗqe5Ynd7ISLd:Kv9yy1(`J'@)Ё*=&BZ̅2 C#ˉR(OA-C*R\Ԛ$=+SV̄)Ae̗"eSҔ>)HP3DEPSԨ({PՑ*D:LvիkXŪKSRhMZ׊V:UoTE9O @)@r(_ X%_ $t@KA}a-+YPֲz]Ag?^6f5YΦֳEigZҪ6u-hQ[򶴵h[vMne\2j;ⶹ]p ׹nv]uRx\BE/vkV9+f KY o|m+׿Ea_'دK >,oUo+ftd Z2-Je|R"ű\]bk3*"ڄ؛G6i6OviW0հn%!K|qL$*qb `o/+:hlQ +xvd[#ӶެHSdMr.8Pqݽ{WgՅ,8 B \ jq?  qV`8 p(n`U 8p .'ֽ g9H0#V>D:af@-<  A-\uBE #A&1 AN\=['Hu`bD̉ D 9A/To{ {O7 y;nם@"X /$ :] @HuĽh=@ROշ;R'Yπ3'S}mtD˞}`'x ~  VJP + uzu~W,'<'rm&JwJ# z~ #'H'JHJ$&8J+@|!G@+{486H% t&H,HJVE8J3(K؄4 *} {fggx'r{eȆn(}= |wA}цgwyX,k(|Xbmlt1u!gu  p`x?qŧщA||W׋h,Xu( {qV!JPIy%PpPu 0xV0w(g? PpU Ѝ8H pX;&G g|aXx,H ۨ 1 BWnnّ "9e$<Ftf t4Y80ӓ1>;@IHC;&YktLٔNPR9T6nW..&װZhNdXYScdmLGJ l=HUH 8w7q:r=0 rbFV!+aɖuIփo?x)K>Iyg7~7uU'yGww؏FqaHWm@GyIti>()ٛHQ{gzχ~gzwYIWǙҹd雺Yh%i'7| (C )湖D _a?aE_y__1#Z,H+XZN<E!&:1z&ɞ穙WQjd9:N7QtЧxD QyВeid^l:?vgyIڟI2ъȈxW4Z ZɩFkG :ceZ4O*6ю 'ak*.[Y:Zzؚ敂sjJ#M jíݶq]1*,l誮`:گ:lVzk :ۮz U۱ 10$[$&*,.0+2[6{8k4<۳>+;B;BD{H IL۲F۴POT;S[X W^[]ba; 0j e gk+mq+/Kumc|~M  %+ ` p iE в@ Ԡ p`Р˲(h- ` ;s* #; k$!4; j ۻ [ۺ`;[(;pV`$;k뺰˸+ $K@˺$+r?; ` @P \ 000 $@&˴|IK g ,k){ŋ ׽1p #{ l; € ˲  :\к  <4#/ kEK,ð&ɜn l\  6 0ɀpƱ/{ˁܿM:S̡[,rLלVLܜZϒ0V  TL}V}}΃ m&+C[ 0P p%K P#K\ K UlYMil}nm#K@~tN {}ׁM]̫k +>K&m ҧ\` |5MT<‘Q-mL1$[Ԁps|ƨ0 hu u P @ȭ;=1-}ͽlԺgێڭUM `]ٽsLeŏm=UMЭ mns} gM>]=N>p C| N ^׻ \% %),-ٰ,䣍܄eIK&;~V7[].K~NN],娝M }4nh崼U.!`Ԍԙ\%; P U[ҊnٴL],=\mϝJ >-Я$=).4k_ mI?k&*Mp˓-6m`+Pk~%n纾lԆ `Î @@Pl9}ڏnЙ0͌ L^ʐ tl}.a-u\m|!÷`h ~ 6Kv|ˬҠ}F>I#kލzl)Ӎ` 6 3|âV ƸVN@aVci&k d= `·Vg#kK]_g5i_wVKOƐO _?Θ/guK$H+\l l챜KN;K P/t߿8󀻵/?.Oُ?/߯OԿ3\+8Mo7H@ DP… >QD)^Ę"=~PH%4RE+]< SL-iLiN:yPBRiLJF>*RSV kV[fX2ɖ-TZmݾW\uśW^}X`… FXbƍ?Ydʕ-_ƜYfΝ=V{ UG7Zj֭][lڵmOvnPL1P}[pōG\r+L8sխ_Ǟ]vݰmSG^=ݿo^>{/_~5H:zAz00A<0kPB\B'$N=0 PM C dE /@^Fo1G7s:f1H!$H#f#2J)J+2K-K/3L1$L3D3M5dM7T>N;3PA=PBENCeQu4RF!A)4?5ϸ4=1TH55BUeV_eVYQSZW؅ZjV^'KXITVUU_tYfuDPjP#j&=vNp%jt9a=Gz&94K,I< ٗRb_~ϣC~cնYr$5vX9M54c=RCsdAM ^j=jq\%5YMyW@ qgc:Y*AZ+:=O{b'zGͶmBVbt[]ԻYIH|WdhHv*RBKdAnθ!=ݮ|=9OM-n;jv՛$!6gI2p+Ya_JyB/4b/p{<= ";*}EG ?5G_9jǿOE[-? bQ~GO|!A !p4x"q{ '(020A.= Ox/l!MhAp4"XЊ:RS Kt!o(ސ?;\89yNA>&H=>| ΃O(rz$#)IQ,&3Iѭnw[֥C[j3[%#8Il8Knl 2L1d$Z֑ 5pE/Z^h`'(C`S(*,IK",Pba%Y`]#bn`R@#6}!;ʱj{hu%ݢE禊`nbV-<G؋p=6(},5 `}E4> )DT[>P%TiYFeRE\a4XUP~TuVϦU=rU^Uʫ֪| z+,~sEVZ̺}_WSTbE'6ֱld%;YVMa;bdYvֳݏ)>OEmjU[vmle{mnu[ 0-o;\7+q\vb+;]VH#]v׻ox;^׼Eozջ^׽o|;_׾ LU&XCp RJ%9A-& ]bR&6+NlFq`Gg@uq紗?T^Zx##FB3 $:H8Hp$'W ,c4#n/G䑸u+5#FshK0!ڤʗƵg2xƿ3Ly]TSy il Ecg6|՞mLbfAMSvGɑL((m#J_\ /Z̑UkB^ How #VqfuE>r'GyUr/ye]^us,<zЏ` GQ- Oro uW}?Mxյuܶ\{aԑgG{վvo{>wwǻw M,D9 *a({d7#KѺ#*Gn ë xyt)!'8T sG4{xG%spij%Yp G^Rv{L aɤo1ܬvљ軅Yt6IugI|3w(Wr 856˄%{sƂ?@>@  ̾붏-@جt(oA5U[Q5WX[=Z@8Y;"/ڦPVWCY4#0'!-7;?XZfcs t du?D8?3 >3ڨcقw1b='[BdxcyD6@[l7,dDGØ><,s-p%WP ʫEdAD5Ej<z$W Zľ913?3\|ijklmnoǓ+%p4G{3rF>DD^ F^dHDvGdK .RuB6dmTJd8(eQ^XeQdVeUHFeE~ vd]vvQF\cJT[>f\ e fFgg )@^Qkfbnf`>;aeqmNfModinmհju(Omgm؆O}g}gpf&h~Fh}.h耎ތV脶hv yIsb.dbnw~vVXipvef ]flgj|H~iVZqvi[6eAfHfZffcidWjjw筎j&6FVfv@c[{뺦}D~F>m&+(7`HƞpȦȖƦʮTl>B"f&&ӮRV(H׶وΞk nmf&aVnn^lnؐ^lnm֌0i.c(>nw2aoťW(b<c3]oK󼎠'ΰQ<*O211u2Y(O[0o󾏼 v `sf|:DdOX6[Gh-BE'g{t#DlwtD1τ&cHH|ΘJlҦDt{rZ?z} ʏ?L}WDoD׌qyj}o}w2uKB7g~Ou;7H7gvs7x ~{oEJj|Q` dȟ?H{`BG'Rh"ɨq&.UN ȑ#.l!Ƙ2gR1!Gx2ĕ 2t&ҤM$QN;IfR%¡ <+XPsI$GWXY,ܸrҭk.ޤ6Jq.JpI^2n1ȒPk2f/gt2A.m4ԪWn5زgӮm6ܺw7‡/n8ʗ3o9ҧSn:ڷs;Ǔ/_5׳o=΃ tàI?|`)x * j`=8!ZHqjazq%(^+D^b9"1"2Gc7(#/h\6 $BcF6K8%ح@Uj%]z݊%ey&G:[&q9'uy'y'}' :(z(*(:(MݤUݥDHrMz \Zhzljkl20k륬ZʫDX'2 hVAN=?h$MblF`}, VnA /m[ﱺ[,/֛)˯2/Pg _ Ћ*EP$ Vm;0ۀ4y32K+ltL/+,ECݧŮP+I\@-h<$t DǷ( V:?-qծ5 0,l"-}F5'kkI) pJ-d1yF"Tٙ 4&l4U⸑h3ct$J|e l+66O+n}e?HG&##tZP2OPRVzw7V9N_Z6bsZ|3AN85M'C@H.)s y[cLE68R a09e! v!13L!lwBkXD dl0)beYU6K\;ZUp%g76-vP1e)bW'D,)PӞ F=Nԥ2uJm*TTR6}UJUjLWÚTof=kNRBnZ*כƕl+^PW65rk_ֽ -_XvUeY#kVk-5;1 05zы tx,Zb@%4D$=C)T5bt1RضT@?p[tVjO+\6lYj\*we0ًJWMkg [HwRC-oiϛ^7l{_W7xK\n!o|{ޮ%.KT` ~Qbm쀷]pPEM0 fva BVeC(6T`iOw z@ev1 K0Gdh6 L,2K_aߛb/2K]2M(WβL9u&]V(KYVfRAZ3Q7]<|v%.Ї6s=hʫ2YJb%j/mVՓT| fi*`bw!(>SY *7Y.-Xz 7:Υ} 4q(̬2K194L^47M}Ɦ³HmOR»8\|~iY p_Dzk[{qtQ;ΆY{|+L]k?<1),͆A(E7vlh;e_{l-A!i9-SM&K%Af8I2:]ӯh{߹gii`=vnbŃ;~x#Wml=[ǿw7E[_޽2uKMoS3?;зd^]:ӋS }:5Au{-<iv?v|{~i?nrlCwQ1K_)%^: Bjr ͹T `_ FMe_ \l ~` G_u %_KI·a  ra\*WqTЁV.` ~! ʡb9!5@aKy@/a  a$2Y Ñ ]9K]VK.KiB V1*M !ș"**\Z)T0 0(&3b22-^b-6o=]5Y9]ai_P50պq0[abY;BubO]=ÓyVzee TC, ƕ$c"t, $-)Cr$x$H1^I&d/ $$|d͙VyVwM$tdO$dNm"= jӱFj$•]z樀(N ii)"ii2KFN)V~\;e=]~)r | H T. ʚƩV5| 7Lj|H쩆֑4jͩ<9;zuO fTOHjqD*AG㧎*{|Qݨ*ƪ*֪*檮*>ʈ+bȱ&.+0> +N+i^+"kn+J+~k*j H+Zk+«֫k+~+4: *L >FvkŲ+^f2+v,>HhЇX8B@„p\ǂl>;4 +n,*-b&- MJvo/n/o"V0Z񲯓 خ'p/ί٢SAAl.Spgo_pc0ݮStM1 d5tOߦOrmT W+`0#p.?NN*j6.bqjqszЊo R$<Z1D jHٮn1r 2q:iO1B"d#."L)¢_2҂rrӒr22M WPlb,+*!l11wl2.0/3. >4O30C%K36k325ks5oÊ3333:r775;3<:=3>> D b3@4fᶩ@4BCƃtB7CE,l3=4EWRZFot*GG*HtE#@I4KK4LǴL4M״M4NN4O+)aOtRVOGz HK0Pj A4MlD˴DDSc TSSJm@-h,tLD+l.WB@qpqiQCpLAL ` XF_[6AD eL 9EF-x@0uA, ,,D@Db' 31^ Dv PD m@6ppL'm6ц!EjKsMAvptJm xT0s@|p,?mD8z7đ5.A P ,@;Щ?P ) 1P 9A QI,QLQYlaQiqQy RHֹkLR%l'| LI+R-%) S1,G?3l7ДA֌;SOzx= TA Uh4,TEmɑLI)K1TM9OA UQI-SQMUUdWa}Xi[qU]yu^ VacMWcmf}VikqmՖoqhWqMXtm7WvݍVx字 z|ݗtw܀V"kU]b&y5=\`\敽i}GqUp nЛop6wU%I_wa YnqYәw_|~5vi'&Y'~^ iN+ 2y=)b*1z` Vu&AUtPWnY-8A),D llSHF V{G9X|IT"\eD,QYb1E&fъ[b rŢTbE,~ehF-dcȸY1h#G@!X=&s4d#H6:2 $#HL^2ܤyG4$(x$щ\X"Q#%IQґC b , cڒ-LT20@̂s)Z2,gBZo@/Ln43K}"lD;\A # ok +DdžR/EaQF{1,jk 1&<`;f"+~mqh*Źj@n76;S wM`Al;MVQ߆W5m|5_Vl`W؉1J%X JAVU 1DWwdR$8mt؎Ummk۶vol+­qυ*w͖s IV!qKXYzݮw ވc P%Nފwkw[/V헿`oj '2`/e:-4'4OAl;ƔBbD55R=OVPAE6`Mve)OU򕱜e-o]rDfe6ќf5ykv4YuLg#Zэ3<͐+iMқtI>Lb. jUwZՎfu- PraMWл浠}k@[|&v|ld?zٚVv OM@ mI?[nvmpz܉w|ntzݽnMmH bɻ n|d߱{{>nYi$}];'o׍qtkT8ās\Dy០ӽ@'ң1yeD$y s ?ϋH':t~?=Lz҇7Jw֡\`֧nvo^vgua}z{vߝr;U^=m={ny+^蛇=?y̯A~zj(6׹WxǞ's$/Gi3>mv}Kϯ7c$xqsz~.?g{nZ"= 8 K?LM!ܠ^02o:>BPFJ6N"SZ0^Pbsa[ W-f0aPQO KM  E C ?M #p p٢ 1 /M 0p5%Ɍ6dpC%R^q9 1(P!M"+ց"1Q*4,=Dü6MŬ]a1eqimq1uqy}1+$mlY!` -'̂ H`4@=q!b=ZF9b!hSz'A  H|A96C:q'q r'ja'tA!N|CC8 zV 9#=#A2$Er$I$M$Q2%Ur%Y%]%a2&er&i&m&q2'ur'y'}'2(r(((2)r)))2*r***2+r+++2,G>`,Al-öahaDڲ.l".2Ԓ/ sA̒,0U$S21-U0/s3?6-R1 $ !, + + + + + + + + + , , , . / / 0 0 0.-,((((('''''&$$$$##$&'''((((, / 15666666666540-,,-.024569=$C'F'G'J(K(L(M(N-S4Y9^:`:_"8Z'7U+6Q.6O-7L.8J,6I'1G)F(F(F(F(F(E (E))D,(D+)D')E$(E"*?&-7-1/12-84*<4)=6+?<.AE1BJ4DM9FO;IR:S]7[e8_i7cn6fq5hr4ht4gv4d}4T3@3222222222222222222:???????@@@@ACDDCEB?=;::?<{@zAxGyuV{hd{biv_icVdQMcIIeCHh7Ki+Kf%Ng"Rk"Tm#Wr&Xy'W|'Wz(Vw)Ut,Pp3Gi;c"gbk)vYn݇~pߍ9f`DiH5dHx%Xb:]eq֙wWr'^t%&ihfGν6$I9Bۙ%aiǑ栄jhV1GzEqPYZ ݣv駠j)eDѵgqJQ|*무SV+G*[aZNHɪF+ZI5KvP~+k`榫"+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z \]FHP&L W0 Q(8! sP;( HbP\H!NXC`x,z]]11e<35n#9vcCǙJ` X+bSLbQ1.IdF. mT@#9QJ^2'C9JdCިQ8^b!,EIPS]|d$'Mrr41u\pՈWı6&)@IN0 3@$,ji+ $57n :9v3@)j@ ХQDS! )OzS G5r U< R*"4$44(G 2^B +-5 )S ԰QN; *B2Ԣ M E0U5HUTE kDJ^u 3+XF8!N(#yS׀>B"L g ,AGvTk[*F6]w}+VJV[2׺.+dl 0 $CagGX \̀L1¯P&m[)&̅FhnHO3 @ղEan3>7rlUKP5/y[T\I9^?- RR0lco@VZчhv jC!.CpA:MV $H:s h"@E`œ.:R4la,cŽckQ)X]+VDOZ#8QqZJZa{#'5Qa#MbeKAV<=OϟsA^N" e Iӂ^'Ag8"jh33'Jb-Zָεw^.  b-c&f=cKP?8m_P9H<=t .Mnsˍk+ 36NO@ZG,.GI D3e)MZޓW;qS:SցX2Ŋ# %XSS0"yDUB>MҗH$6a^ Y $F6\GWǾ0wt3 Ҝa &QR{E;Xv7d@Sf~qC/}hU&zy* jjzϞcO}NwދVD7HGdO.e}PFSl~C?pntT>q>|}sw#x~|}!4~ ( x.x}#|y}ׁ H]#dWT6h8dxIjxcцJW^ns؅bnQd8F~x`xuxX.ȅpeUxKx(X}XDXO'-(XTx;~lMXgHh؂EȄ(2!N%8}DDH3$Ebȍڸp{x蘎Ў8Xx( 8yɏ ِ YyyYِ"!9(',+َ В4 / 195ɒ7;ɓ?79-YFH  @Ϡ > p ` Pհ P  ې Li򈕽 `cY  kI [َui^ bIfَz}^S`]aIw|闔 H ɘyyV際َP ڀK` ( )МiKٝ H0 `9 cI  U ֎X yip( ) аuََh툠٠jW pHڟϠɡʝ0Y5jπ ,Z x**=/ڎCZGZ*Z ЎLzN f ٤8 `  IDَT*Zʥqpڢi 0F&* Ѱ* ?ڟJO PH8ꎞ jڧ| Jɟ꫗:@ê ڪ `ڐְ` (9,vs: غ: @ڨ d Cj Рڝk* +Z yz@ K`Yɥٱ8:Y  +R/++&{h@8˱ ^j zߪ쪮]m [N ȨRJڟ] KP! x۴0{KE+A[˸Iix|A뎡 k 8{{ͺ+˻[G˲y;[P{ۻ ۼQ˫ӻK+֋H;)Nk 8ኰyߪ X{kK+{ L;  ;{a˾Hw۳ۿllZ 騒۩x Nʹ+4+9 ꎹ*6J'|Ĕ)ēO\:LKj Z|\,g kd,:C:]\P,ְi_۔ 8 kLy*:r JUêyЎנƏZ Ш6ɽɾʬiȻ@|ʞë ؤ>M_iB]Hf9]ٜϩi ]]mLfy \)UmW j[jz͖(}m]cݦ אmx-ؕ=]ٛ=ٝ ٟء]أإק=ȇי-֯mֱMϳ-ϵ]ȷڭڻ֗mؽ-ýݵͮ˝mͰ8Խ&X0 ܖPtՋ/N0CȊ /Ƃ}0m=.uH44WfvQh.z]cr8^/^|.}ިR nb({0/xQt.0иsB^F~HJ3{ q |L..wqfIK4Lr{a i ! ҉f.DxW.''cN+N*uY/Q2c!r.u rN T UTv!*~pswrHYG%t4Us~ >` NVn!_..>=d6OwXQ(x%TVYu>Q nf^!~[zv>ﮅPcQ>6gW\vp\vw%w#Tw~>B^BNB&V. OwrG`fVUxq e~a mdN!*P x6eyEVc_dQsA/rnQ~$lf=2zhzz}i&a!fn vlQ0֑>^7Nnm~~EztGyTMt/EFGoEF_ɎU_tC:DY4C/㯟$f8HB?OD # ٦9Oo96S V \ƵnU +`s"*]и~F."\3e!2A 4"-f>rmm# +\sdE9`%4ī^=և؅ }nx YlA[׌=U lȋ0bfmL 6 ڗyS6mP $0h_~f ~>%0hAg W1(wq[Ѓ(*oo|ޯ"+q@7qǛ-oan5sdD݅O&@lil(6k(R_@Ae7΂6 iX[+ r'~;9.pMtw*>OBǰ]{m4:j_]䗍RG?vybgYWt'6 Ỽ?~̓ӼC⋍(e_cĞEC?ih=_l_ʉ q} [0~O36+r#L9d@T@>>T;>>1l 6ۆl0عdؓ/؄IH?ck?Ac@8b-(WW1 ?cӂv vЂ",>>4<$OF <,Yk ?$#:Hf+1PlIݫ8b19#9C$nsIJJ KK+K빟(#6PHM`ǶLnix{JCbK[. FQbKoмYJHh;KJmLlLLl38̩7m;KM[MlM|w͂M٬+Mڼe͍MM N,N<|H#]-RR %ڳ|R}$B+MQ6{ S}()4H7MSr)E:?>@A%B5CEDUEeF{rpRG-K=MyPRS2wDVTrYUH2J\E34(O]a%b5cEdUeefe\a]V gV-ViV'V>A om,9W&I$YWW!rv5bV'4x}Wy k%$ )a5WTxU~XrEXXi4V{XjX ) 9 nM#Yp}0}X!Qٜ Y٠ŸؗW2S)ArTh$Fō BZZH 3ڱ}۳ڵu[~[[Zý Ņ -[[Z]\˝\ܽ\ݍ!ѭG mԽ%ݪՏ][A4)iڑXأomY]\Xuz`} eYV͑Wy|-%Y W5W}h]_]!0i_O׍x_ߠ__61ߤeu6M=`U` v`g >SNF`q1pXW0c1y?Ȅ#2)XmwY1j1( ~9/ƨ:"#֚"jy!.6ayQ(-q/bE1*;YQ1kc-U$(6b7q`b/zR:)kd١yIJKGdyardR)M&xddV>:LR8⩙Q)^VQ PCӵAd 3E+SR+E(.:6zl}n13gfxQ)k%.v&GsΨoVv&0_2d>r&gO3,Eq)~mPy!Ȣڬ,&c/0PC^Li~v铞%̢f4B؛YӳղbȉڣMJબ-=kghN0(klSX-B~k^~>.alZT%[Ip̆g&>#.Y6hmvngҮeV܆Z&߮mQvgu>:edij$β161ᮝ q +* 軉afipv9?`0Hg~&n.,kkoWbV0 p/ W Og0j1|޲J5 ߗB#C.Vig,b8ndxar1c;%%rr%krw1b~23$hn=.r`.;#o*j9sa#aiyJKLMNO/V*R_%q0Sz83Sw6Buc{ZT;T]"ڞ`X;Zc7!uqdwv eVjklmnopq7x3'r2g&N㍊ℊjj+P@5n;F*jV\?"V@Rw>{&\Qxx[VVi΁lІl+28gн ] [l>춾k)h ؅.xN'etnn+":{4U>\0j+r<.$G'|nS9I|YnwQY];6k';P&|/R:-d ?Oy}=%|Y Gu4RR2US3ݟҖ"}]:~U;P~17P8#~*PAkoQr"3UG/M~7fчO0]~ OwO?7,  )H`2l!D&Rhr챊#Ȑ"GF4ɔ*W\h0!A2i ˜:STr˛BEThϘ>m (Ԣ.GSenɐjRbǒ-k,ZA?j mڑVĘaVMN9+'eIbJ.!ɕVP~_y,vyߔLg$m%De]xћyYӒ{ԧ|:hHz(zg]:(J:)ZzCR)z):ND**j1+Ҫ++{,,:+J;R{-f-Z-~.ӎ[.К;/{ |/20Cܭ"#B9:d2 (1k ]h2%*6Lck`3c衒&uz*~CޘJ$!XHvRdX1@RದYadU'I=[ԕ(@]2W4_-Xu$d-m !G4MG.'>YꥱAr1qadheF4Qs>{֧\Qwd6f5 2sBEJ,A^rKꦍ+2_8Q1N[Ӟř}'?a9tsBG~z(9%jPꓟY+FLN6W,9_H D"Z.f6Y'6?rẶ ms i< oVlԟ9CyxZhBڡU bA Wq]]VozuxVS "5KiLm5WEԵo+c6?Z}եU;)+YJlezـVj G ZֱEhZ[֯_@!Y*c'{6G I;RR{WV.nW@]Bwҥ.q[arv[v ,wK_vwbn@Q] Uv _NV8xUot 'Z+lP\V$&bBV2^(kmaZXW<>i[7~$X9hc$PɳV86n;|V 5oyj''½\4&9DwHp6x%Cat7TrCEG:ukǝu'[MF"E!YE}C,C._L"۵ի]YlWݑ/=Xp{ moH~xs-W|1wͷKAx/7}Q/x}wy u}qvk}iow V?>3>/jnG8`P4/SbBST%؋@ ~ӿ6,IHE) =R V^  pI순dP$A8_P  lFg\ 9^ 2ytIG8 &.axGZ *8h] D9Hn!vጜ0! !!ơ!֡!!!  "!!i"9(="$FbqJBDA&XbC6pC.DPPC^+d@+h$",*(A)@"CX'*XA. 0a^"B,63>,x)؁-65HAH)*B.c."F`w "ʗ :;c;aiP$L#D-^#Cd6^Cmd6d5D f  Hdj X$lPdhlޣNcD&D&AJ$Fd@H5̤.H+ C AB>P%͢HBT1r#A$0@88"3Vn%eH %BHA1*5c- XaP=Fd%Wƥ\Z>^|K&:8F@5\`&aa&b&b.&c6c>&db2h*h葦H0$P0vxqL@锦B|ƕ@f`p>iqPL@t)A Ɩ闶jxi'F`q")ҩi&lD.jijA:j^*F*Zji6e0EnBJjN*Rfr2nfk*j*jjj.~DOr ggffgjHH@b++hn ,l"d*2,:;2b n+gVBIzjlȂȒlb,Hr,v콺˲g*)˶yly:,֬^gRFg:і'~>l&rBz.-JqZ{Rl:b-qzmpsjӒȊ-o۶۾mdƭ-Cݺ-mB-r-ԃ >&A`&2'zAT@4In1Hpyd@10wdJL(qLqMTC.d_XqN {%;WJTn->Rnc?0-"s {Rv'g0+BM2CM$%%MdAH#( AX61s A132eYn#5/?A>("(r_3A\Ye5$Dr10dzGԥ1l횲IA_.7==&s?+tAj2!)4B7c#b.6ZOx^^|{zAb+Fs;@{D^7"{x]4>/ļeF|{B=ȋ\~~+#i烅V=اϠfɆ mQ_L,G~&k(q =zl苐1$n q{E9V^ p154HvjG3L4R8,hN5T$6O>T7 P;%ND4=% QI+e4Q@5QH)TTNtG; SL]uV=ROUUVkA3UV[յ9X]Xo}5Wce6Ll0Pljr1g]%\d ›k5PgL3Yq-61Du m5,·c7Kx1 s W;QŮ6c4Eƈ-Ҍ" p=% *l(c~Dq;>hdF i᰾gbVZ1+c:ꏮ1kޚ&My3XVk^>;8ž9Y.4hCmO PR28n0{A 0#&'JÜc"EKx[U?iLGI@]&@ݛ*5J)MfSH`D y:čs>.zͯ<ʷ{&kF?kf驇d|Sƿ"QdE cܘf?5 lH0@a0#AB%4 QB-t aCΐ5 qC&{D!E4D%.2aE)P8`ů<^(0QS4јF51|\|H0l# 1*"bHI qsb2Q%1I6~\"% FQj)S\rq哴)qT/sK[2%01HevrD/ UŔ#$f7%a45Gu#iNN3w+rT+9H<:t(AЁ EAZONl(@zуj4L(F&r4 %&Iҏ+ͨKsPQK.R#xʳOT&80*Eb1 dMmJFIKiTV49:V#rFeQj-Xh-9Tq_ Jluyk`W 6*bXXNlT%{YngAZю5iQZծuka[Ζmq[քGWQV5nh3us]ht]n0 %yGRJS{K78/ eũ5|47 b"a O1a oAb%6X-vabƘ5UcqXE6r|d%9Kvrd)8Sr7)+oU f1cse4ykff)YB=‘W+d9GB?|72~G(S{E_"ć7x+>qP8np Ng 8S\&W93~\;yKqy=8u~t?t\O9Χnt#}':L~ӛX:ӹn`ZvgUw~bxAj}]O&N+&i_K֐aLyYk՜;|1?~Zn=_z>prE=>Oz.or̿}e|_K?7zfcӗ~:_]~>/y˯଍\OO 0%pDLa1*)j3+a^, `2aFa+5+@&8vA!">0_ .a$4 d2apPy+y!  ]P 8rp" и DbG "rx!ɪ( ʡ9 L+@a, xpY 8AA80,&+*+!!pq<"?B 1Aٓ+ڀ+qWP,t=jA)ZE+FA6%p$&tbٲ 3GMEEcEE"$ǰF"0(F'GYH+hGHkP&T6Ja4Л)C(E(E(F(F'F'F'H'J'L(K(J*I$*F)(E,*F..J-3N,5P+6O)8O':P"=T@T@T@RATFSNT[O&=.512121212121212120=0A1i5798z6u4p4k2f-]*Y}(W{(Yw)[v-\v2Zv9Xu?UtDQpFKiDHhBEf:Cf4Be3Ae9=d;:^A;EB=:DC1EH.HF,MG*VK)]Q)[P-SQ2RODPOPQORTNVUQWXVYYZYZ[YZ\W\`Mcl8gq1hr1hr1jt4nwArvJxrTn^hhdmbocrfwi{~osxiZRNROPSRRRRRUm΍°sd[OGB<98:?ADEDCBA@@@@@@@AIYht㿄幔澡ðǿH*\ȰÇ#JHŋ3jȱNj$|Iɓ(S\ɲ˗0cʜI͛/EJbȞ EJѣH*]ʴӉ:})dՊ?10ӯ`ÊKٳh %kP^ uORߦ˷߿ fE|@:7ɑkε]MӨB̺g׊b ݪuv6Պc۞+oȘS+_μ2R]/*`^w3k ߎWݔ OǝZ{uaIW^T ^|uQ7v X^qe݃($x%|p,e VXS!Vw jb%H!q>W ^lwwPF)T>EU66w\*$U.6^k7cUix9RHbcjzHHy(^i8ٗ.gc5fU>ۡmeNh^rc"䖎E*vQUrj뭸*"rFND7&|p}6ljZ$k~d]btv#JP.sl.Rپk۔v+Yl֋ 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ 8&1({AHETъT\A8%1bF/bF#b- h 2ƃcY@5JG)XcZ4FBq>D HFL $!7v14$&IBF̤XVv#(Q8d(=WQ$.KxSk%0˞Ќ4 ͽPش&6Mmr|!e^r)e9EiN53d:B #p1i g ?Oh@ jo~ӛ&D#:͉RiA>QvBzAQQ_']HSRtzc0/Fģ8yNZ2THmjQGGF&X%wPV)UĬD-2q\e'\zbC Qm#,jUDγj^)KDJ"/"ZuhDPͬf7Y)Ⳡ -h ҚMjW֞lgK[¶ͭn]{-pK\Mp.sK]>ӭvczWl8TWUx{^wuj_߽/~&"EhaQVf9fs{+i9G6TZ:fs ?"U͟eԎӛui:ԁй-* kEuj|Z^A6# %+gAcV׹uhhSYF/hákVu-ZrmA1l" Z{.o`ow߂q05nh>᳁,܀h7A8h9/W? r\]qka6y[ovzXzeiu'ՆMkשuBb&;4+Blg{o+kEݡ|ܟIw;h/[7nPD29@sPgYZJ\?=ne^8肦Ս((jVZې 7y|Xd'n'Zb(]Vfvx`vD8vE u YZy~7wxu%yג,e rWy*y*hH(#]K\26o;)hj58tv\xv7gervؓ5mYgAVwwp4evƦ p(pi rI`*^w biwdi5uye 昀)&yy~8jk~{GeZ[r*rphh-הs9iYp/_ajaFcr VМeф8 gf杄p7c5njQƞ~ZEĝRўfM뙠i o ZL$ZH\&hfh$*z*y(å2\ 4VIsٝ#)et׵Z4Z3ZŤX6ZOJKjSp/zr`cGeZ[b kmJo qʤsuZw Ai}J[y* ڔ:j]j\Zzzz srvc56%2 V$``X45XI5X X T4%VRK?0 HSC;T0R0 ĬX:G{1TZrW3 :׊S uSR3R2USS44TSVr0ǚF9VYuNqEj0G! k>}SWJt ";$[&k4 2:%Pd{,cxaހ 0p 4 py ps s_h`zhz` `h_0CnQɚ K4 <@< q =Krk[d[ w|^\ z^ @ndnP!RQ~]nsne>\^dz}΁|Nw^~n牞叞<=FT E9 o?<E{I{ rP_  &pKq|[QFG~9m;>D,ad ^^9 LEbQ PEӹ|d&@ElE! w’.9>,O@fdlހC pqdAJ8@ca^ɾδ^68:<>O4K2?1ʤ/\IzE"AIkPŰseUi%%V0JITXWe' Ee1FKJRWwbD _CR-PaMSTD%鄏1*YdŰkOH~F % 0  _1;N T¬I% @qХo1xYKdL8U_:ȟʿ_ܱ p M aH~! qD M?:˳9kFC ) KNAF@01NA[42UK).XP@L@=ne8@&RvZmݾWܘ3:Eƛ;&װcɨeXbƍ?SzdEv.Q~Pz :zЂsħC Nh?zQnTݨG+*фThGAҒ-,ŨIUZRGIISt1OR4;iR**G)Pq*ԧNE?{zUJKSVrKe~)Ma }f61E6iYկR4q9sJv&],حcjTźЏnH8FRq%ha)VV#jV1s+&b#hsдa8 "rj--kDZ,hNOWAMiu`F v4%jJFmjZehO/Yך`H wufΚ-eZ5w{+tc ANvQl\ `˵ܣȶ@N;d^GFLV ol>xs ;פ]W$~FqQ[YQ5pMs[޶=r:Ď67r`="Y҈Kꝗ[3?sh<@zҕn/Ozԥ>uWWzֵuw_;K`HCaG{)L#BHW{q"9s{ղ]|8aXG|yU e[񏇼=i,|,)=Y|E?zҗG}Uzַ7[-{}uoK~e{4|χ>}S}?u?_~Z??ٟ{`ƻo>t@@ T >̾HivATC@ DSܽt܃?A ܗ!AgA"T$@%>&lB'l>(B)4#h;k;A*\h0 (1C2C3l'4T5tB+TB8DB9,B:t7()p6Ą7g0AԎBCLDBDFDdDpDI6d'HHDEĊJ$KDNDQ$QDLt6M\%SDTEYEUEOE]LtTTEU,EbdAAA•{ IdA=\ADA@@@)K"ILJ l|JJʠ\?JܻJ K,;K[KSILKmK٫K$KK˶Lʾ˼$¼Ä?ƤtJ$tKK LLKMO9MQjó7!C8sh]=a]8]0(0oF1rpt8\pxXTNΌNNL p`tNNML dDs`;ρnX a8:EPaˊL L`O 2؂!ՐȒ@=QHQ M;iϰ.ɉ=Q!EQ!%pR(NTQ%e(%N5[n֮Uہ`[-[%u5T[)M P; -\Upّ1Z025%:=+( T%a\6ՊSH]2X]$a НX )ӵ]F\ݶ2]$\ͻ(]1-E^%戃-`dٜ7ܷu<98 UtLQH0wGѓ___x[` =A d`Dy f XY`%Wh:!U[ 2MD>NIM!n"6#F$V%f&v'()V{`*-&@i7.'bo1Fc&.4vc% 8~:5;cd8i@n=3XAFDVEfFvGHIJKL V3P OE9a9cYn * HW;IVpCbԲ WLM^vӘ1;b \kVl*Te-W`hu [Ѕ[jh:?AH)ygD}7y>}ُ}XْY&8ȃ 5 ]RtM["UWf9<i@]gE^4d0Έn_8>{N&6FVfCkg)y(j(>{bdz3W m#:K몎[kL35k};6~kjn 6#K8n3l^kEņ8Q뵢5Wkk^LlǖVl3؎{8[;nCl#<ˎ;mFk3mVٻ޶nƙ.mή:vhF dYL዁pF g|ff&n\nn6nКffilF6nfKi'g{n| n_~F wKpGxַPmr.˞VPF9kyUw룫6:mgJ!WmN9l>F7f6҆qq']l p{;7q^q%Os>s}m6)s_sW"EHH"&긣@=:)aa}JE(2K_ 2; s;IE æf頂Le8R6*'(yf|d' XֈZW[%WTݕ&mMePt.A.G&,JٺkW**FƘ;IQgլ= BȾ䭲-r{J&5Rӹ dvΊ u%xS/Z|}ښjߊƙJtJ$Yd ߿ ]K-rͱbeXLƹ,Sʾ2[͉sdpAvx)1@Cm[qϴ %Ջ}%FU+؈59aކ 3mc6睐Zl H=tE9];A4u5|"$Ĺ/.}EHK_9CKyT9:԰#f)+;޿kP_ ;un&#Ͼ/݇_6SiZ>v7%#  hf ( R 3 r C(&!(!%Y{TdXZaV$QV"C$r]<EtqZ`$rf<#(9RЋo#È;汏tD?<$X7>Q`#$$3Mrld)=FURP5.|%,c9843F@[Re. K`eN8 A |&4w%XkQ?)Ё1, F>򏉌%3Jn2'KV畳/nW?fQojDb+`Sϙb?˰cozvu{kS6*#{٭v6]hiGڛ]kC[_m$k;=nr6rwoQĵekL&a|/8-.Ϸro.Jwx~q4Tx];q?YRy)s9s˜5w>q+@zȟ^tCO9TgyiGM'9Ϸ[|]_^_{^w|ns=p?{ǽF;IHf1yo[A?YϚ6 ?[u=azqoXӻ W|/^0 >O>3?_}?܇~#Gϟ=W_YYvn_E nr`yQ`b B J7HU ` f#=S lEؔ x&tVAДM!NTAB~) Z*p$,xTN/k A|4x5VTAPU%DVWDWEU `AV%=qC$`a8DbBAAd^A#J^ByHWA\!XA}͗"-.C"%W]/"^/)3"*K+XR,Ң7f]$|"h%#-#x/M88rC9gA4#i)c>8:bi83BK6D5\؅57>dUD U#pQ1r?yWJqDR$Y$FnW=-GzJWG?NWUE\W FJWyAJBQ7BQj"$!T#}A~@"ORR=>%3Vc98X"DW>"d+SQ%[VEU)<=*}T[c\eS&]2X}-X7t|B&xSaр h7D)Bg:ғ6)V^)fn)v~)tP2=Hk=&j@f9a!9:A.`LԞ2!A8a8 j֩Ad"*T(+B h74&@8#$c~*8!j&V*Y*>9TU:""#_哬a$ՂfCI[LX"D1F&=*>ne/nWjNdj&2Ε:TV4..U@nkX-CqB5E"X>2@PM+;z9jY%8^fY8cA= ij)A)9aФ7 IvXbWJ$eH$MZMfǺ$KrH% BWvYkEI^DN>vx-@yɬ@45H]Ҡ<UkA%;jɒ^2DVWTvk.mBPZ"C}[QE*(zC9\ci"D`fYՊS.D]Nf^­_*`n^=X^EB-@:lBUAqFޖl:j垦/Fi.../y )Cv&R}S~TSHVn}Z/N5Aog)qo~NS($쑆JorJBMVoӐ).pMoDk#+pp8upQ+cp}M  ^ 0A`u|0FiB/1n+11+L,-?9[S'Rda/3.Dh$W1zu#ϓ&1$;Ӕq^ vq2!!2"'"4#?#$O2%W%_2&g&or%K2'2((')2*sr)2+2++Dz,,ײ-_2,߲.r./r/0rIhr0321'33c2734O3G354W34_3636o32w7 38,ZLA#O,4,D298-dzs##;&?94%ׁ.<2B+t?7'A[2AO4$7B7AdC[s#C7X8l(X2ltBgdA./ԁ<+4+OsDt3P2Q(5RRR7&28%wʳ>$#>~‡䧾ߴrþ;8>/2aGYr_?1>M?"?5VR1Pp?[?"q>'HæF<(1@&TaC!F8bE1fԸcGAI@ LX K*GƔ9sK,oV`y&^01#,I,D+Y<* t1 BؤB'rI.#LRI$"\R5\31hH- S1,s/DKl7S9[(:S=? TA -CMTEmG!TI){&J9OA}n)F(CMUUYm8iUWi[mt_ԝa-c RNiVZEi6؞o wL,MWumOm6)ڭ{W}X .NX3X)X9<Y!&;Ya.+[V=%&ibyg袑8=;i~:訥ޙo)BN ښi~Y^m~C$Xd]`)8Ѵqq_{oa7'_:?YҕF}nMg}u=v%vOW=xԇ/Ώ."-P  fP# a?I%$ EBN'$VCZP7D wB pzgq;@L! '.H\D=#VJ_vqE"IHE zϋq|; rc4EtE:Zu$d! H2nF.Y1Cmrdպf$NT2k^UnmX%|VX>R~y]r.+&hbR,3}`.mI&.:h^3qqe6MnB f9)tN$&8ɳxZ =Ά퓟ė7$ L JP2J Հobo`19܁Q0$]A-u A,r(BL`oԤ(-|T_20)M3jӄt )IT*7N!x1Ӄ|* $Ҁ(d7zpjB-lap\XH< r׼A+X’!!F_#jY"$k5+-nDbU)n[ % :9R]xxXMEd2& P]n-nK,rCw]tk]tv2%x{W!%v`_mBރ =$qS$uEHr E~᮸׻ v)N F!z j_8Z _&&*`J ^@NJO%&t RbAC 0 \2yP;laSlٲ7 /7X*3TΜ<ʫ2TL,s8 N<} DdZ4 cA.aa(FqA-W A8^|u.jsM}^d Kk%{_ruHk[ẛ=ȧCmx{z>qEnVKvZؕMqi#dds]?K۳Vowx!Wxc∰Qo2!Pr`3Mv5=q2; YshHn@o / ɰ 0 p ٰ &aPbJ?0.4cRJIJT>VI>DT|pTED?BAQR< qFNc2Jq40eXJAZC\QTzZ +F@LqSKT&e3,qODAEg{1K D1A13,4P0űZȱAK$41}WH"Ks1!$:( : 2Y$!p!R!c %;C /;E"C#1 |PAJP?ElU1_]q%KBp1n#CҢc2Yrm{+VFWb1R<Cq(1( *#,q&QR,[MƲV.2A",.mD///30s0 Vb031 11!32%s2)21-335s39S313A34E2?s4M4M4Qs5Y4[5a32U36i5g6q4o37y4'2w311(7218939s9s:c:c@:9;8;s;<<93;)B!1As1cH|1s"A$>1 @1 1-AtB!x1>M??0sL"1 AS@ 1)$4tB 1 @G4EuGt =c@D50D=T?EDM@0UE]F)tFkBoT1stG{G HtHL3F>s?SJGD JStE[E3FFsL4|HP{MϔN41CSFRD4OBLUMLєSIP 03=/4 uC4J@4LSSwUO3"0Bs7_V5UHT51T1(>1ax2qIu1yK tSXe1c,6 0!=c]]0yIu>)Sft00J 1+F'aFc`a9q3^^5[J4*Acs``95a!bca#62H Ic `Vfea pV>3_S_uc;cSdc`deUF%vPm0i6faefkV1?*@hAcehhd#iiq4jVjmHgk81y `lIVVavmYVng6nVjPYisk61d0h d1{`F{ҵF56YSaVgG_tjPqVZkAV1/7esV\sGWt7FumQ"h^A^)^c@z3s3 Vi W2Qaoicu>z3{so{v~)e72O HeH01g31!7:ݗww7mx2Wyc 6fWOuuV9810x||W-Oq;>5T{k1KdMS5ne8`Xr3=R{sI{iG7]Sak3uaiUiօ_X,x^V38~8xӊWxW]%yyw^՗ooq?ETK8H a{1\Xz+Rr˗4H;2F)4_'XH XAYAU\SxtK5YbbCv[PԘ3uj-3179=yOٗٔ}vco%!oiEmYqS=9CsZy@řo sؙu ]I1:78=uiEu W9)sMAڍiTA5rSWJ֠EYTxsW0q>a?p Zڨq@y!yw)ScYyOyUy{eڪ+YqtpsyxS6YYLz1zKɚKvAv1 :Q{{;?+X癴y91za;ڶڵew;ڷZSyGӤ:Zc{^;{z{wHa69=DTCG[`uvZvۿyYՎ{}7_8p&ywٷ1#N719:1%oW P KN8p]1uO)xc5#<=ܡ\Iud?>I1Ov{]~"V^x^>3>3:5L>m5tc PoZ^|El`Tݔ5MYIIknM^6GY;q4ULTG;?N?t-@K!^a_H~U/^4kDyU;;<\CX!SQH-S3#>s2>۞EZyI!+"S2" H AL<()|1ĉ%&aō;zD1Ǒ$KФʕ/| K"cd9Ν9w,I&СD{43)PLW"m eԩRZ*֭Z^u*ذ>:q,Yj&ml\pެk7&޼/uYiSk$3'S.|=:dF>:լ[F]ٴkώm;ݯq ċ6<ʛ#g<:pҫL5ueo~M/׳=ӯo;QUBJW}*g)^ IX^f9 Zhm)ηu(ҽ(r5:Gq! \DxdqF&MQc(ڔ7jY#2z #-IeJ !UY W&bl'v~tމuޙh{'6y&ڧ*Z(.hAN):jh馈VJiszjJ꫚:ꥸš+B*kjkj[벟j,:[*N;kZl݂~;j뭲鲋lzo{mir )'dk8Sx?10$ 6,™|22k2'<3Uʈ*o~|  ڬ")9¬A M[#i5שQbY{} imܣ%-uv7{ _~ xwv6}7n3>9fO]9 yt+yޚSߟ=zsx!**쀤꩟{9lO||/|?}OO}_}o}~O~柏~~Oߏ p,*p lJp/ jp?p$, Op,l _p4 op< qD,$*qLl(JqT,jq\0qd,ψ4qll8qtR#Akԁ-o9=WC$= 1JIr,&׀$uIKcJs|$);g27M Iy(t6f+_Kg S a x@s/ !,!!""#$%''(((((((''''%  " $ ' ) + - 0 24555652.//111122 3"3 32222 2"!1$"1$#1%$0$$1%$1&'0*+/,/.-..0./3/06/17016.25,30)6)%=%$A#%A"'A"'A!'B(C(D(E'G'I'K'K'K'I'F(E*F*G(F(E(E(E0@4>7>,812121212122222221222229o:@A>};y7t2l0e-]+W|+Tx+Ln+Gi,Df0Be6?d< !?D"2Jt"(D)R?| .Ґ^ c (2fL)Ш2a|#(9^vD">+agx~l 1x`p"@<y$).`sR4%[RVTeWbIQr!A4i f:¡_ s0c.3$3̮Lה2fnf4yKr,g%/Hs@Qļ>~od-JЂDBЅ DKЉZ(F7QTj GCJR(uhJWzP0UIcJSVδ8hNwjST? MҡzQCjԥ!,Q"ҩ;m*VAխr^(Xjѱդg${1dR1bNc(% 2LX4 J9A$38 ;@r P)͚VBd8$6l^),jeZ-I_+ہҶ|! + ,J`2u=e*T␜tQ>,|!1Ca6!Bfq;Q^%{:_Wm^տ}paܣխ@%rQ 5R ' Btk8aS ⺥z^$ NӞ=q'Ws8M<3Oγ,bYϊniG8Z> M˹}) R9Ѓ39MDԓM-iF[:ַFwOг&]kX:Թt=NVľNٽ䴷 QFMkicN!1\!ȧ1 \?Ln'n\{=aSXŬmf[׉ nr HKil|dfmE>;or !a~ru!|H{7 =9RߓҽA5qWmeuv__mQֽ=8/~a mnJ5H'{w8v̠1MtW>= y7m5vzn l鏪 cv0^oN4DaX]e^B :$y{{gVd~X$wֱ%olRC|UoXSTPӏ??%>Ev * ;v!@)hHz_ ɘ@s&a{`zA9z6JnE:aPc:8)Yگp";@k;t ;;5C; :UZ$[&{(*(r..0;4[6{8:5+<@B >;F{H˳EL۴LNRS[V{P{Z[Y^۴]ba;hglk۶p;ot+s[p{x˶zh;Ɛ b۷˵P .` 1{k[V{;Q۹ 2K yV . + 0 ` 5  k :a k-k4g Ð 6kW`4K`- {8P ` - +`| +Kk狾^볹 r@ -K v` 03 вr >K ˺K0 -; ۲ܲ r p[{܋- K'l)+-/̹1,rо.+ {drK - [ 빥˿@+3C\/k P qвp p ҐżV!d\0/kh,kjƑ IrLp̾: @_ C:S@@ЗP:@OՐKW:D]:+F a~:Кzlnpr ǀvE|zRQ熎爾ʗTYHO~蚞Y@闾NꕎzPWJ$J}Zn.벞btHKW뺮긾ǞivH @. 'q{:qVqVn֮m7lq>pnFm.kq.o׮Ԗ.ln?OnӞlg/_HΆ(oqHy Nn~EYe{f^RO?[BD?FoxЊg]^L5Nn'\g~HOWo_U/nGXv}x_uOqos>i.IO5皿M2 $/S`O9o9YNO^?ʲ/l1o:__ *ʼ__ǿMnIN0/9?_?O=AHP РV HNqf9rt QD-V|T &|X`… ^SEcmR$I('D(P!C3٣?cuYdJA uTֈ5m|a%H!C\ݾW\uŒ!l#`*XU/Ncl_,_G^UbR ͔.VhEd9a3DMVmlGcmѥO_MkaV ʦ-Py{; ]:ǏgØ.aSǟ-W%;D4O?F2pdi!D ī(e$b2C5 qJě4[ёA|1F6 FqhA1EqŸ"=Dr2K-c("4jg$I4Jj*tdD :\#,"9dN߼Θ@9R9N5c GQ0+Cu+ES TPH'MFRg֘IZT(XiPKV[!2V:6 cP|z04 !C$EsLU֪%FEuZpn}q-%[sLYGUcJu[<F2(E t_ӗ_w`E: yv_49IEdG&yKk֦im] uynK=(UK;"I16kah0: bz H*Ϭۑ)h/j!oW++y;]ӊHR=LoDz"# :"5:aJtDYJ|6=b|Y(us&"Ĕ'\\M_Bw$vUg?=qg]KhGyafǵ MݑJ>qGp{fw 2"ꄭ.ahC3RGB ]x8Gح9W܄ @rOMtS&H@^p# `AI#!l!,> _uh]hqiFA!hDrcbE8TE.vы_c8F2ьgDcոF6эoc8G:юwcG>яd 9HBҐaHF6ґ!gQ t;d&5INv<3 IRҔD%&)!P`嵬KZl%pTҗiZF#LG8tPLd&sD2h>ʤ1il:؄6_RoZe:a:bg<9ONg>O~Q9PԠEhBP6ԡhD%:QVԢhF5QvԣiH>0ʒ&#JEEB@ꑍASErz,[ `龂P7*zJTէ*-Pԥ RU)P*xzncRGl0GR'ԩ*#e%u4+jUխIH^y:,&u]ja׆Qw`mXr5-l>&%tG=qρ}V%;UfdG5dk,V%V[S׭\}_qu{\5lh4! JcV+}.kyf,fo m)k,հ%s{؍ 7Eo{{\◽m_IZŪTᔪڃ] l%ݙX6!7t@ ksK.1:6bsO(>'Eb T1X%qpGxp7x%>qWyeZJ^y}I@('Gy2rQ[/933[yq?<v;Gl^vo{>ww{wT|G|x7w|%?yK|5o|A?zsG}Ez֟~_{s~&} b}~/'_o|;{o?F5xf``+v/ B*8& "W=9iyģa˿+t=c\@<;0;AOAh@?9B[H¿#:t;Z apDC,NNJKpJ\AĿQSdBZ05I{JVW$XE4>\HEEL<`FP b<[$dD[?`8p̄*D˻EeLt\_GWG2Yd(C56cDGtFaahA}tpy=B;i03iXD[4<LRſPENFjm[[ɖDž;f#4]HZMB-AڕO,- dL݈^%J ^ 4D %!^ED]^B eY &Y`<I\X&>~C&e |a asE޼aaab.b ^b;~bbbb$nbܳ`%bcc .>cMc]cl89:-8rg[;@#F3DDV:*d?^Hd2q3q]%]XNN6eXdj0}q-V&1,&+Ӳ!#'Zne[.e+9l]`#ۗCdVf,Zhijklmnopg6brrf3Bgu6q8%hXz@{|g}~t>{^g{hGhVVh~hp.聈hngh2a8hhu`Vhi>i璮||eJ0JgY鉠~Nl8{G{-h灎hVi<ꮖ鯮vvF굖6kNnjfjkRkn뷖kĮV&~liediP4lviIОlk[jΆVvl&knնN릾hFn&n6lmYali.^n떎1:n n c^osh0q0qx'6 ph+gx&x%w'r&wr()*r,W-or(s*s/_o+s.r<)s5O6r4's97-i;sr8<?'C8Bߒ끚?EsEKrc(g }Pf`tNrsPi(J O@sMLOd|mxr!6ha7b7-k FhjuhWvdrŖ r7qfCfoώc? gmYgmp'mNV.uwj_ninGkkhm6 mws xklvwIwѶцvnFygG2zٞp6.hxnVh_zOiq^znfWz ߇d w񈨄B&lm(Ƨ"Ǐ"wɧʷЇ9XjH"Gl~hFNqا}il}j TWf'R[b6g~A>eh1*q|BAȉ(+q89*訓@2l!Ĉ=PAbŇ Z(#ȐcX0Xcw2C 4%"w쩑6[<,j(ҤJ2m)ԨRRXViKtqmJaT/(IդXvP,Yg Vmܤ a 6*lKA:`_fTPQ2T4ԪWn5E\I a&5djVDڶν` /#'f؝b‹n_(g³[_g o>mr0VD#-0~S/ X`Ezc1u5a]!GW}$?e1!M^US<#A 9$EFrhEEHQ`GbyHP"P EƔP$TC8鸙N8:&rf ͉t߀yC>O@LIE:\ yDRdj)zz[V 9yC t `Dq%dZZju$VC9+~VLؓЈFfdڮ:,J ի`bʸxXU{ٚA~;ׁUATL181n&U#^iEK<1>210OuB99beg,*\\B3|_G "c9KPТ& r$%)əT1QK=5 U_u]{5a=6e}6i6m6q=7u}7y7}7 >8Nd=;8K>9٘K9k9BZo硋>:+|a:뭻X;~;X;{*<<>;>//w^Ϣx6KLg1dBalt0BY5͏gP}.朂P !jdiټ XHxv% 9B2ڊ] ү!{qD_VU OȁF+pϫák? "'څ2$DA U.Dc~jȨB.HD)(G j%㊚/G9/o|5YgobO$F>iF=T9BJ3q8q#(h :%* 0>? %Zh%A !2$M(gx@' OBKvDXңKLi %6QL jbf) b=Mqړ9ۂ-q"DEhNJT:-'>e٨40m9,.\AE&FSGQ#iPqrE!Z%#S)d5Cd6* ơk]ܺhF`FK?qQ\ ]n p$}pcLKrCT)!ԤR$6ei-cbRUtdFV~zl5BͲ,hC+ђ=-jSղ}-lc+ۤdcqls[-pb 9-r{<20up+]CrtmC1 vw쎼}/|JY/~[:W~,׷>0V&ۂ Sl03ˡ&>1SX,~1c,cx61Y\<ޱ,dF>1"'yNq,es8Q2_|,s[2G0Yc.3όf.yXn 8y'F:i1<@yЄflDyьr5hc45!L|Xi`Ė.5= j9DE-;4ȅ ޴Uqqh;C4af([̰EK{3clkS޶U\ccô Ɖ` `{ĝu':w[ޏwpt L[7]L \`81C) 9w󱓽lh;Q䓫-W2_Бv0,{ĖEAk9p{gbՉqa\q=Gm?w5gQ^g p{.qC@w֟uPPP'|v_xX;sx$$< ydh)H SD[4aVtN`mϊ~!Ann_w#ʗ>w?{GG~k_W/{??~ǟ?_f% ! ڟޛD _j`}B _YBC K߁M*  .]Jnq]M(؛&t] 6`E6iވY9 ~ˆEaNaB5t4؂]ѡa!ۋyu!z]` B!ڛYݡa-T#ʁ&\$DP5,2,Ꮱb*"a"%2b%8".~$*b-(m1+<1ab0%\ح](+ژ,f[8Z8N[99&>Ђ]Lv&4Mѣ:K; x5:zX0 đ醯%PF%6NU RjWvW~%Xḟ#L%Z%B6#|+\eWA]^[ @C`&r<T6a.&cjENcFdR 4dff.E+l&hh&ii&jj&k/IPl&m֦m&n &ool&p'ql q&r&r.s>gpBtN5'u^rZ'vnpj'w~nmz'xgmygu'{&z'{'||gy֧}~'~v':NC5T(N=dF'J'6s>(.NhrV'fhwrznxB1] 6$TFy&{¨~(w(F^zhvhzxB&B&0):BivJRq.)PP{@^ij'h评S쥒iiTC–9 @Bi jEɡ*Aɠ.*6@iJ*b*RĦ:BrjꥂjzJR*Vj:ªj*ꩲj + kz*"2Vk^Nڪ~z뵆+fk)8-*? T:%檹n+֫Zjj++j쳊+, ޫ+k/u6PĎH5i'fǂ2gfhʶhNN@5tTf1 W,olm xmn"GID=%Ch=@zhrfhZ¬hޗd%mf"zllv-mҭm{Ѿ-.ۺm-ܮmgᮨx&.|:n6-䆭2.^nn-nٺ&閮鞮^DZ~9-.X .&@.T6(T(/6/~M,8/.Wn/v~//h///i//h/.g//OX ̇B1O PP511աPAtPPAH5Ly\pj0- LIFJDKDLpMDNl/CL+PdCC P]PI\]9V ,<3-]q#!cRLf`@vsvrZмĔ?/T+هIc G5{!p[Lu|:ցT S9y#zyyk9ȒVG,.We㸃Kqe99+~kAz+쭺 3?+A+?w?@(@&TaC:8bEG^|5 Vzɫ@*^VXPI#R%ŋ'/Jv bшJ@E$ʟJA49 <z 2a@ ;l{]YpeX©9*دqiut'Wn̎xX?˦<| Mo!bnsuUp YvyXts9:OXdž p?P C?EiG>ݝ/} YX7% BP pzsMr,llWB(_y1>(x:ƾ0_ {./E4E=%Z/ĕ 3{[:xa^'᫺=Dkk[w'&u,(s,Rby_2Xgp ) wR- yZH$%IVRRHiK%dp426gYE Q+adX1m+!WI_myZՊ/bV&.̎os[ұ_ `f-NKǰ:'cZt^ d. j AŌWrcH{m#BGS}sWTz~5; Hvk飀Fa5ū^k"a1`%\}s`f@-&Mxoy`t\0!ud'j*vplǘ^Gꔠ>8l:PEڄ )H$#tԅg(Njʽ28XYoO.s`Bb0.ZpQ+<݃eEk`OYsz?/i%oy/"l# HJ_$} +kҪ!r,SGNTe3/$ k%i&h:UU8,mMOg-mS^2g|KeMl;!|X-i2pYݖ?x$)qn#l({(λ |%H-Mq:j@ prznvNpwnٟQN"{W0+l%\p]Ovx(.P]~&eݕ /$WDֆ܊[`s Dd*ZOwb:ٝkvu;nlo鮞C{]V/ xg%^wldz}eyd^?? oЊ|C8_zɫ~DgQ^ !}_owz|7|/w>|O՟~}o}џ~_G?_O_pO #ppp 0o)a 8.-pկMRpQpɯ]pbapm@- Bp0oPp 00p _z~0 O ӯ P˰ Ӱ P  0 30z`P p 0 p Q 1PY,X#q!1$Q(-5!I71Y+qoAA1;0PQOq31bI9uoigm1yq1YqQa1YQqݱq1qQQ !1! 2"!)r!1r -2#+R$;r 0e%o&P&s&m&g'cP'I^S yr2r]%no!+p*)#,P,Y+,-,-%P-O..-i.R///0.O0/0P1 11W2P20/)2%130352=FS 2s3A4p5]33Y_2 0gS5a6ES73671623 ss972k!+`2q9:s7;is{f "f(s 42S0SOS QR=0/7sgAt@9]3%B-(t@@@Q q"Ir$;)BOSSAED!rDg$E92D?RFcTGQ$1GatG}FkGq#F{tHI4HqEiEJJ'ETEE/BIH9DJTK 4T"N=RLO!$_4MtIytJ IMM5%E<2AR[;)u;tAs>w'a1>_SSTARRT:C2+TSUURVV5 WWOUWVXWU8XOYQXђYXkY)YRRTZZӒ[Wm\ZW\[] QQ]5^u^^^5_u___6`v` ` `6avaaa!6b%vb)b-b16c5vc/4p+=cEvdIdMdQ6eUveYe v Peifmfq6guvgyg#dgvhhh6i6_Y+_ai6jvjjj,A)D(E(E(E'F'F'I'J(J -M*4P,6P*7P%=9p4C222222222222222273b:x=<;:9;<=>?????@@Da̧͇ηγНmZTUcсŚ{eubmahyfVklBhr6js0is%hr%en$]d&W[&[R'XL)QK)FJ(HM.KM6NN@MNFLNHNOKRPQQPRQOSROTSQUYWYZYYZYYU H*\ȰÇ#JHŋ3jȱǎР1Ȓ(S\ɲ˗0cʜI͛8sYɃHU)͐<*]ʴӧPJJ)Ɍ>Ux+Q[#DaѪhӪ]˶۷pUɲ"%XWU_)fE(2@>6?ejW\-?cV Jق@ AX! V{$*-0kaA~C[z\ |-VB ~[zE ruoyd|Ag"*gf>Y `.+~ ovLC8SOWֻQ }!f-{sp,`l홷&عWZ q[X;x?=ֿ>B #q5~V˾B?]e"#_S1';[\IGf8#p ؁ "8$Xȁ&*,(؂02h/86x6X8 rqq8pN) @P9ٙ(y `)陵ɚYIi y ęٜٛ8hYйyy̙ٝyع ⹝ɞ9㉞iiY Jyɟdfjy'T1 ZZLTЍXhɠ"ڣG)IbGzIJK MOʑQz%IJN)3x ,Xf_ @ w9QIHSzwJy*{Z}:U.Z@ؖX  5Y؎8yHx` [ (yxxy -8(Hhy 1k Aj$ * 0JN0? q:JJ庂ڬJ"]zOʮZ + *J; {˰˅[ Z {~8&[,DHCBE Reb^/Gǀ6}@j[Pt''^GtfeR_{bK]a6ZA0q2|A~|~%6}akGk~~~{~D^WdY[^^ Gp{s[AOd xdY`FN}+;A`gL }Ŵ_K_Kk-@9ۻ;[{MGJID0KO>[B6tU#CcAW~~A5 tMe{ۿsK$M4JL K,L4\KKK!LK <l¬T(le+J*; @g_Dքç:,H=?A,C E JG k ~-=^]Ӥ׿t؊KL=قLى]=טז M֟M¡fץ֩Mثحգ;Raza8tqK9xvy GQuNx-ifi6hjYx NaSU Vi&hfhmR  FmTTV 6y#NԊtND '-tOT! !SqlmUApvw&YjlYtm8uPep|GW(W*nhA 0k>vvzuul#PuW5S"elN>Q *'~PnT9RGum viuUaY^s lnjQqNgy=v Gkvnix^NQn}7|V]:Ys^_[Aodof܁p!! RGpwooxxv۪QW=@'sk> MN߶pLq QS7;S?.N%r1]Vs%stR+-G /17sr:GH~87Teg~Tn/ owOr&p1qOP ox ?s͛9IH GRusG\rl+!GxFh>nntt"^/^>nLN/w\Sy^iEZw=n` [:h%m?tq`Tleǎw`NAwvPypiPlENapkW/ vY|uw wyo^LAfV䎿S^WV~WnRuXEv x - (Oݕiy%xGO_Pޢ^k go'ꙷyx_^C|:GP%h4)TD-^ĘQF=~RH%+

TR^ŚUV]~VXe͞EVZmݾW\uśW^}X`… FXbƍ?Ydʕ-_ƜYse:ӶehҥMFzp4Dlڵm]k \pvr͝?1ӣ_Ǟ]v{^xjPj 8ݿv-_~0@$@D0AdA0B 'B /0C 7ó0#>c/DOVe'hi'!ȥŊ.&"Q$hyb4Zv)(Kq'?#$8$ϑQQXYNNR! x4PAkPC-PEMtE,LEX RUh:CQE'/-#Uk9bUW_UWaK[kV][ŵ_iW[="c]^40<J/mMnN [J\nQhe4$!ҶM%T,SU8`ޅM[0SRN̳at!F6/Ƹ1IuA vp\=- ;3feegfo9gwg:h=LU#;fb>#ƺňQVP@뵸Zl6{i:-vkufZ(+[!Kzj?սqg.1Fs~⎜sgsύ='_-cm43vKeWn[Wi}ϓwvou7"zribF VD?}}hZ?~_=uWE~7;ta^&w썄˳ x%N3;R,^J- B8aP)|Z`+nP5l Of,*K,n{[7y/ȣ'PlL( /\EENGtؘqHYژ9Q}S!wQ,{dǼ8Q,#(12qC$ TW+{6 %md'1E1suS%:NmTWM$a|aDf2Lf6әτf4)MBTӚ״*Mnvӛg89njDg:ՉNsӝ'9Oz֓g>)}ӟ?πԠ,A*ώ3״9hPz(8/Z͈N4(!:Qs$hHQJR4-uiEazd5p#ă-r [آմCDaaM; U&'r l6T4(!́\՛E.թZhH4r)9HWT:ܔj4Zͳulݦ9q>[ H+YjVBC ,!z\`3dײ򵱏5KXp6]l5EѐV,NM*vmYZմG0 pa3u-A[,Rbn3fDjBçwяjw.ZZ⹷jL5"D P$po5QYpb}_V.PMs`B(`pkM[Lح{;_lr z ZxU1!2L_!q;K|omqUN6B61{|dnV=~K{ܣZn8Pլq5ӫal"ՌEf79?ޭ8QM}|W7m!mZĂ L洰Di`n"Z?63!ib^liE'ӧ1=MbB̈́Q"m5I]X;zA&V JyaH>m2Y-̙~矡M8[@v;2vwτG8F|klb6tuWQTk!"]؛Rb& {_3gp3r)| }: ÃG"qy-ձO"gnRĕ96dk~>766{Wڄmm{*{'OiEzԵiutBNf7 F} wz|oxkp=W# f ='~iC[=X553󣧽Oƫ/g/'sܟ^҄8>cB7oMw7t&k /n#S C@S@Гjj@ ?[;+:@ۃTAxp /x󳟳4K?0Z9k ,H{]&S-L6BhO5#kZ‡)ْn*+8j-8B$&1̹`;/D5BЯC{C;5C06/l6n#+D@S'D&9tc+p:}d,LLoKt̬|ʜL '̴Lx ? 7҄LLմLK(,M\'׌LtLTL4LL|M|̆LLL'dKG4D:74iN9 :`qD ."Y HOԁ%B:OϮ5X:Da%-YB/"/DbgOUQ aOZڞ;IPQH UpO (0"eS4&uR')*+,-./0U%UrQ1eSҐȑy~/}`6TwIM)~ٗSQ}Jjш7Q|<ٓaPGy@TUm%UuUC MTTPQQ!SI M!"Qg a!"fuh]VHBYIjq 'm q!sI%rRKW Ձ:Y}aTQ5Uqx%Ԙ׍WU`|.y΀-؊1@ّؐ%ْ5ٓEٔUٕ-T}X٘ٙUٛYٜٝ}ٞڠm%%ڢEZ٤eڛ]ڦژ}ڨZQڬ٩ڮeۡ ۱59[ڴڵe[u۷ZO[Hۮ۽%ھ۠%ܝ5U\eƝDž\Gq׈Oܤu][Uݣm]}E\-\Y]]]]}]\m Y]Ǎ^ŭ^^^^^b j @V0=U%=f!b_iE__=Z__X_&6`U@` V` n`v`X` 6a~.`fa_]a$&` ab'^a `,n.b,b"Vb#c$6c-~b-c48Va0b8QA UE^c -d=XM~ >d]ԭGddDdLdN e ePR>TieBy岍eWeV~F\\n\]ʥZVf-fGfJd>eOf[ieff`ff}fKnvkfreq6[tV[a_fsgxey^we|ggKu%}&7e\S!ص0h_XS:uX`'8uW&&yHCF{5. մi&)},, Xx[i@*Yj.ل؄GFL-iMMN]zi0k5AkPk9׻TIXjN&Ƌqh&kUJյk_% kqH^͗JP]-pľiy4׷)sXөm ̮n1s  p Jo]\WpΚЄ@v9XSH{k@B&6oOo4oo Vyu>oaL9Ep)8[hUZS, @O װp XAiՁqqxl A jp !ߓ ~ƨ),-./01',UmU v+5 ps 77'7wqs6s=:tsBs <>gtxt(tQmPF'lNONHtJ7tT'ut1lEĆcYpYdžXc]?vauqP_/vducw7mxhS֮JGa(pvijjTom`oWwI_RAs;_yz{|}~'bSiZ*GN',]`h0hPqqhU!G苞u qʼnWF&y[HriPxjEjUYzC5jFjz}TfGxgk^Nz+o&{8Oa3 ƶ̎p[q숸_Xlxl.m`IƁpZx |lVX_Rrt Ownn@&1n|׶֡npWNJ)T,,|XimF/ߏpK)9|{Ԯ? Lj~ 7U(ih „ 2l!Ĉ'R~qЖ<7?{iсC ČS : $A.D*b8 }vЉ"Z"Ɂ,X"5hf":"#A !c:iEJBDB'Mj%xBVĤn^j8&IV)%D b ܛmyg#ri!9%ʉN&sC(ZX5)~)jjnnꩫʪ@;ٺ+qjKs-+ɦsY(1{Q鬲)┇n[P-V'>k䷕תk.R;hlRrK/FmQ迒 f˯>fN1_ 1yk1!<2%|2)2-29235Ӭ93=3AE}4F4M;-OK=QS}5Pg5=[5[6S]6PKi]toss۝swMH{ݷrۃg=013 387+~xwbw7_fόK`v3'DIͱ^D<޻Ͷ>" |C-3f9 Os#@ 'OD8?{s>볏+-Ҷ1s=-῜ebx @.#DL8:=Bt#-1:y;E,f XЀ &Qxp _>~?İq:+m$Znf }F sD0g*B,"8 -kKL#$.mwcsoKtϤ ƍM|{L/;{Ʌ wvVТzCpghtk!}&_Ɇ~%Nڈ{-ڋy=5ic!=ڏed ytH%,şsqD)ǖPpDFemyr_y̘CrҬ5>k3㬲7"@P8|G k\A-0C3bVpxa#tV tY)҄hB-I%+&LRY8hxaҬ5F+_(KIC>ܱE|Pu}O*^v[mg'P~~״i`xRu+_ XZ-o6Hq*uW鷰Tf%Dz^~vXju):b"Kf `ĞΰzZ?eZ|n63F B F!F,F9ЁQ/vϋ˙A:SV:ֳs^:.f?;Ӟ% C .="8 x%Y|p cx }mG??__[Ч?/ &- >`V^ 1C\` z b `j8^Mہ_ a)!2!:!%!V!a  j ! aa aa aaq!f`$ Na a!!a"j"*zA8|reEH&zp7b'"))J*"@|*¢*Т,b(ޢ*".0b-*"1b1"*b2 +b+:c5"2^2>c3"7R5#8z6*-b3J8c:~#;#4#9#6>$C#A&$E BB:dDڢF1B9dE[F$FBzd?zfRfeZfb&ifrxgaXd&fcj* jZkg&ѦF1&jf&o>ofMp& f%r rs,=zH'tRDsG YAD.-@<-Cl.gcD'T'E`y'B<*-,EƂv-CsDT|ƒœrhTD"L,@("I&&|)dhZ؂BqE"UMAHnT+^Adk*Dzk̰ND\FD(J+ 'ǵjH>|8b޴,@fkHÞ lA>,-,Zl`lh,Ǫl(0,S@-옰'hbƚ^j`d.+B|TH+6켙*b*Bl@0-Dmjfmƫ*`^m+EҞmB8ktLbpD xIJ~Xŷ-J[-@iW 빾mi&.zE؆ET8㞌 ¢h^DNB(Dm畐CiCݪk^FF`l,b.%/,(-h jktgRɰ`BE,@$*l(yP/Ư/֯///˴@x\/0eH/0gȌ4O0Q<g_ހ`0IZ C\@p ǰ I% 0011'_]-Fx6hBh{V(vBٌ1iTBi)*ĚX,o0ױQHgj6kU,&ï5v@kIhkB+uq!!'Ӵ\l"R\)Yl&E&AXغ-)+Oò/*~BX1B C3 Enom|3g56w7388399ԝsHg= ƹ:3&>#=ɸY\3>Gd Lhھ\=3~D4Ṵ5\n`4ҩȪ3`Q)H ڲZ-EuL-4R9`Ics4Ad~~~~~G¬A$yW73x;7믾ꟾ~~'y;w~~~{??~~G[?G>AAk8@&̟? `A*LH0*1fԸcGAHQM;xj. x̘kCLeI{Mj fnD9jY_AS`=4D KENZr4ZW/ҥ,E13hrׅ '-E>vu.^M[v폷soq,70 65g_?*8݁+1boΣHm>:﷈8K 10X*P5֫ =x9>("MÍ,|QFʪ믏?C.  E g '̞R1..,w~Z2.1*i Ĕ =/!4Z3‹ $ Hno:۹ӽ<5>.PwѩUQ <r Y)Ҕ2S1;ύ}GeTPVQUX35KK1p]5TF-UYZETo -QuٍwZJVi+ /sHN.wC6XTf] ڏ7~2##XX6v'(+ᬕ2[3*} O1#Fk3G9衉6:衋>Zejq%Ě/:"&WT@e#t9Ja@N'/JSee \f.mڱ:L)ҺEk[Hou5pwFGHlvQg祅nWGu٭vui=i݋8-"u͜f?FR8f>y{)멯=蟿bXHRNj8cש9.r#瘷?Mv&1b|d@)IF*3^V%<D (A΅ G0D!=yXH %YHcD:)F3|- C'r !բC:F(81 Jtaf`hČˉL-CЄɢF/I# 1CKy"qlyGѢd~_ӡZ鵱 dik[*-oW miAŋ,Wvuf;YsuۛH9Ҩ*]vxjKeMZTjhM_f;.RCcj 86Ù%L25jnAB_Ac Gb,QUإ9V͎yG51d"Yɍ4e`d3Y4|ekYr\0cnʉOb3x͏,sf8yΤ ̄~ D'Z)i -:~ 0iMgZAjQԥ65;}jUխ^u]kY԰q}k[׽lQZ6l\#[ͮRjf;ڪv=kg۞vMo&7}nj[fw}miCwûwx 'x ~p]+ gpS;h@PoNKv=n;#ɕ]r{Vyŭ Ϝ5'qc0t}|Џ|0okuK:wэu^?}\GM=dc-uwVx |r{܇6wM=w.]5 ^~;I#zϴ_/_=NP;S;2 3 #0BS8;qq-At   #4& 3.;C;9 UB5ETU%R.S_s?RUuP[RQVNVWUW17ϐWWoT YQX1UXQXZNZZnB1[u[[[5\u\ɵ\\5]u]ٵ]]5^u^^^5_u___6`V\ a`6avaCa!6b%vb)b-b5 r`r Р j.`,PVe1e5!2X .}!N6eWV`.!g`fcfviiiV V梀c6fg.a Lgd.l˖lj d.Ak @@dVl@g1Tvp p p[V@dgV2`kix@xdr1Wsc'w[68W27e2?u]ua7vvqUaq ! fwU!tIrM6xy@xe7boc2(wJxg&MGb!,''''(((((((**)'%#"!!    ! # $##""" !$),/3555542111222 2#!3%"3'%/&+0&00!241:4?/A)D(E(F(F'F'F(I(J(J)H(G(E(D )D!*C!+D%,F(.J,1L-3N-4O,6P,6P*8N(:L!AJGIKLKQFV"C[!D`$Hg&Pm(Uq*Wx+Z~-^2f3k3m5r8x;|=@CHKNPQQME7I2222222212122121212121;-M+Rs-Yd._W+bP(_M'ZM'SQ%QQ%LP%IM'FL(EK)DI)BF&BC)A=)?;+B91G<9I??LDEPDITHOTLRROSRPSRQTTSUVVWXXYZZZZZZ\Z[j]by`jcj~efzg_vkRnqEir;hs7hs4hr2hr-hr'hr"is is!mx't4{;}::9889;>??>>>>>>>>>>????@@@@@@@Jg~ؼĻ H*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͛ɳϟ@ JѣH AΝuBTիXjʵׯIRΨN#6*)طpʝK]bVemZd1VỈ+^̸c:%u/߼5(ބ pE^ͺ׉,h*/λ ed{ʞE/$iðKNu͡-kpggИkvrϿٕ^{{7`Zmd Ɩf |%gar]X|߇ (D[{W*Hb=_dw#x IBL"F:򑐌$'IJZ̤&MC>!d4HրB֐= 4YkrA2,ĀȘe-o);HIM$g&rA L)K]ֱĬ)CԏrY FZR23pLR#*dANSӤ3ݠWD*gZj4ej! O] Ac5mo[o{[bE縨ȡr=HBz:3E%MSA7\b]HlCԂ)28+ r5kcdz݁൑hPn%HP<`A+S߫\v,VAT i1KW k EYh:Ũ@ hPw ]qAblcVxTH7 %oDL*[Xβ̺sC ,XDb-Zf5ϑZ zFYbt^ q}ڗCH#?w&:(; oҼ~GiwF G viЗJDi``pHs 8"x$8(X+*(,؂592h%;H:X88r?x3ALȄ=C@CHxZHT@؅DxBhRH_f*Gj+ۤ;K{{k aśۼ2 0*/J j ۽뽆[S )'fNdh žQ MA|`ѿ ;Bp%L }\7hyQlI4I_6iF*,.02IQ579DYb9J Q@ BfdK&}8JD,DLJLLRN C OW,dK:AXސNQY\MQQbSd!L-0.F"^&oGC>/;\Ȑa57H B r&PJ@bYX.^tE|JdxĘ>G>^fӸCQ%E.C)0U_^]t å^δ5h>X>C> Cخ_:!#o7~7 f $dWLQ.a2y8ONߝ4>W#D?=_GdIO3} V 4q$N5!;h+/~b,oQ7a}vjpN)uOY_u4W]ٌ,QV%Eg fy?Lk_veywaONsH-@zтPa0[B%o_9=Onl?Oׯz(oֽAww@B6^/t?X_Qc=X  ((C FĘQF=nH`ȅ$/B4YG-]zLeZRΏ7)9Г<IhĤ >UT(H#WW3"V+2oeqŨ@NQMsu ۪u36I4'JK@, jUA zawxrǑS2ə7=teINf٫Ysn-tmͶ}1kFfe,Bl#4ȁb7f3mv]wgsq;o՟}ۻߍ[>on >K;0–hf(X#qC P[lAuq!| =\qp|1;ͲnE"1>ؖD֖̱'!CӽgʲÈ++裏 b8 GTH/4PArP 5*DjQF2MRK/4SM7SO?5TQG%TSOE5UUWeUW_5VYgV[o5W]wW_6Xa%XcLs"d6ZivSăL r[o7GqE7]uUYe1U楷z=7_}շy_v&kq(WX`u{;x߂/̸҃c?9d6vTdOF9e![F-^9fgfo9gwg:h&hF:immivZj:kZj;lzlFfmv;n՞nn{oo~InegǛN|//<9 ox_ׄ5]8^M (A<)Na(@0P[rHZch(|Csbp!p0>fh~_C i,lXi^S B€bӞ<1<#OkmpCCǦقXǦq|Ӽ"b8qjl#9Hi,FBʑ\8Rp}P2&pwt(ERlմ0GŲC3nT&3&YrsjbNq3X˷5-0& b4l,HyM )F rrϠ3H85^:jxC@Lc6hYFd A<g:QbJDt-mi-Ǎ6mn ɜVr0'Ք l }Z4#i͘bmMPUբ4gjNhVvOz4 t]AЁÔ=Z" Z1OU@5^YK&u,4Litf'>m`d;HY6m+c >le5-4C mmjٿq"lvZqVT{- űO3Z vj^=g9ֲqRëִN׺t҅rM ]et]m*{ڦ}"ށwA`/" 0 q(N8j7r` ~M󊍌ʪn,nOܧa)re>KkFr}ߌI96}Ӱ>r$3 X!3L[ sռg?OԔoR\8ss(fPZҢ&r۴rrMkFbP, ji ڇl9I IB*O6T]#CN3v½\0\>i+{ v7s5Oc߽o $¯KcUwxKtj G/\_rc`g˝A8lx+GQBMkJ9az•`r3C)UC/e0N;;[Jgz|n$:Տ>iw֝>r87oe9pւa:#.{+Cu R =CDDR EHgr( BXɑMDOLQG!EPThF|D] OEؔZ Z] UOQ-hA_4cDSkbLghrCApamnopq$r4sDtTudvRSyzx|}F-| `LmK`rN0QWx'N,>+b2v@W݈^c (1b+fbK)lg\bn /yA Č`d9ňCvDSLxҢ0xSS̀cdd̠:K F$p\`ƈ:Af_n_*Ɠcb-dΓ"hev3Y`\ b0(g3r.9x<66b!0hK@bvz;:KkcouugA@5ax84I^[nh5~$]0㕆ֈS. biVf ȕ6FVfv꧆ꨖljFvf&kjA@!(NXf6jF .Ĉ6ܤk&6FVfl&ƶ96VRlLځ>j8c0moH]ȃltYi`?)/I]-iAxЕ]:zU1 6fi}`浃5D g܈_z]8;jֈaF∐tonv#1oo$aapp 6FoEoC)rɴvގ/ΐfi?jMNAβhh&>qIbe/c(LO{nw€:LP$d̐f9 SEWnQ.85Ro C䮨95,_(SN QYX - ]q]q07рquh~Xt!6K7eK!Cm.n=sbFfG&E|vpRLWGx>g)fFihV0t@kƈ82n5^nAڥW guQm1v1!w׍Ni:Et'9nVt7x'gwƋxHC8çxG r@Nos[y7CApYz ڠ7GWgwV@3T-@@@р 0BBWAzAo{727{›{{{"{{l؃KcƗ|r|އ|צO'Y{&rC㊢wP89;+Z#'{J*oj;x&;P?*}r}O} '"`* *Z#eF)kb`:j c0NqRh"ƌ5!"1a2tHǔ*U j%LAb&-9z'Р>W-j#ǣJ2U)ԨR>j*ѪXr+حO冖$p4VȅM1HrnݰMǖc Ozr$rhSK"]ŋYܲK9YI2(&@[ m!wf{&6``{i;ݻ-OM|y-]9u3Gn9Wo1\}ۏw>~{]?{'`vteh_gar!n68aT3bcVg"4K!dY^4RD_#.xx9cbTS5b`x1Qpȥ!U؄c Ix]h8Hg5A9ZffyJ}f'F( Z(%(E?t>4QBK%e\dCmZ9R}:Iw1QvQ)&5XTcZ,yh}[l"ˆլ]AXx C-pPda&Ĥ.Cdo9-#>???їrNE(< ɢ-A9( R 3A/2>p&煹i;CLqp Ď̰:QT%vAaJҥӂ5PM+jA8M!kƳ4rBK%3#e\c)O}$r6 M-7˴<(e1L5t6lf&yr0L=Ͻ:f+zun mKEג IK943MsӞt KaŖ#>5S58Ǫc-Y{H_s]:x-1{-a؂hp~6ؒy:־6(d6-FQCVӭu~7-yӻ7}7dl?8np*Kb\(m1Q ȅKb oXYC tp8TkDCAP'O[r!yoÄ> U% FOL1Pn`?ra98A Kظ @\p%͠; !iHC9ļ1ӋD|I ä-9|K@|'_^|gQg&FKs۞ohL?-L.)B3:<%В(0%rH/%}R}bdBѓ8 ߓ/ ZʟK$_`ԕD& LL 1K@ع1^__Y  i_zK ( y F v M3xq 01@ @*@P " ^7XKaAz`Z!P*^5x1СK"!! =]`QKl a^g*2 _U_,b&ab0A_#L"1KKhB3]ř^ݞK+\ A3Pa#bgD88 95fcL m?c]K ߹2"@cK؁4!,X:TV\OXݵ2@4 7 aѕ#•7؜KHDQKdKL$Hd*$ԩAII%1bKq"ȁYPù$WVWvX%YY%ZZ%[[%\ƥ\Υ]%^Q)PP^%`^N=a& &b6cc*c>&eV MYfn M& q&hf%h&jj&kk&lƦl&m֦m&n>f巵PK\S&]b(rN!՘s>'p!gHgrZg4gsO* t5]ԵUv2grb q:t*$*gBg=nzgu"!ha(p~6bꩣc}A<F&hP|'(nnu>'hR肊h,/]vގNV]^)QBj >  杆 `eB(Š„~W~VĠ~Wm)%j *&iPDעfg ɡ%N[""ϣjue|mPTwgg@2*u)}~aj(i+ i)LK(#=)_(kf?t~+4Q9%(!nڪ+鉆>:j(<: ADl<M(~Kڔ+v{ި⨻ґv~g2h|, kVOxΤPdOT"D{鍦,L~m g -nJlF-o>mJ&Rnmlfr-غvkZqPئ-e?g"&ۺ-έަeݖY*:k. Q^bmɎ2Vj\.im.t5.)+.e.^*>.֮... /&./6"o>N/VFfnb/~/~/o///¯oNүZ'\oo /0@7?pK>30o_p0N)B)0@/0$AnB&% o$HK('$PBo / C/B' qf61/"A/1/q@ S/X'p!k1{11/'oqqqnrs  W2"(rp#A/%%#q&(wq1(o2 2op+;@-Kr%3s({s8r's*C3 csNBo37G9S/@x1/t%%d'$/0</=WSs%3?q G'2 CB&%lBC&dfB48h RB&t%t4JJ;sKu%4<޴Mt545I"'{5u15Y)c%tKTGQ/U@N?2VV ϵ&X5``/A%\B[;5TK]_./fv12u"b3vS/;d[uOewfl5x6hsvb/vh6LTUEtHCOB//6g561˱A%+cvL״]q/_Vx/((d|#3tx۰%2J p/d^/hj/V"~C[ 8S63o{wu_(6'[7_1&rS7fxgg{+/xvx(iKSx_xxe8wxtuB ['39?svSy@[9tko cy~ z+V?7tPw`Gy8~tƹ#y^i/P;z}۷w~! kq{9ktF7:o3v%Dt5$o$4x:8;y7r4Ӻ"6;#2Q#;;9;1&Wc{#3?xs;;;7yo_;;&1?|{ _<ƿ[|@s+7('OY_7{mxw9/[;][4[27wt|wz}rCx|_1y<2#W+|+t=؇{=؋=٧8;:/bwZ:WērX{';;{8{Գp%ɇ1k 9{5Sv~pw |=۽'oz'c'g1@#ox=o}𚾸+{#>hgc׫=/s}}G?sQK;3O=oC?o?=~[?O>@1@A#ɔQXf AA?>m"8ǐ^05K)@`4 9D(M4BTJ;y̹OW&UQI٬C&nh)/!TZFKɓMd&Ŷjƕꧯ?nKA52YoCL#d,ذ1@iU/G{1쏴! e9b\AF2ʎ5 hVikS\VJزim9#e&Zu;ňNqGk>!׳ϕ?~}y ʰcK;ܒOŵk*!P !{?>hR$< bŤ~E!D!HFJ<Ӫ"& ,k,T#ǛVdq"G|";2E,1I*ߛ7jO7썵6 .\ I$3JS5ϳtS:<ԒO<2,LC81Dc[!F㜭!ʭ)K$R=MP@6CJ6FQu\Re|5YA-q >A dv%-.aCݐ:$" 4i4l *Wn 5\s_|'+" qN F[I 6Hr"488$'D%6(,x3;Fc4,9-=9ٓ1S݄ʨ -[i^Kfhp=dh>!~z6se-Ș+aZg6xkl N]}@ùjC;pWJhWqSYճ\c@9yXurYWY􉜖lm_sN.W.08A 58׽dS&`˒O. |_ioCZ3^_E~ @&||@EP`A^P`B~P"a #;a  4 B6H;O}"AWD$o*SbEJQ\O!"](0e4јF5bdG9эs:摏}HAq4yHE2tdHIq%'IBjrd'A9Oe)AyJTrRd+])IXr-@ #-c9K`Rd1QYLdR˄3CLhQӤ90!|&_5= NM~So$g9xNtQD;NxQ%^ n7Ìҟ-h@τs+RdC݉uj-G RpԚ$=_0Q/v#HPR1T/IL8ӛ֔9NbS4+StQjTԋPePSfuVTjʧ=*YեnRuV*ֲլ]+VJצ浫h}+^x׵ծ~kaTFU}5k` Î5TElcV,gzYU`;ZRli]kY֪Vll6bY\)?jQf*w}sJԭuhR`S&,'k9\0; xΗ$P})qzj~%IYVb6[m@&?;ˀNrD9FPxΏlt".)z{"ϐ\)H@V <)<Q1 @1"] N)N Q͐o!BohД )VJ!ыnO #sooB+~q),#!΀ er&/!@!ܱ.22n$p͋H |R)8(H` 'O&r,O3'e$b . ] ..%"/, 0{,[j'g`r2 q)Z_ %3-21)΀Rr 0c0es6c]"Rk\$ 88!q [tE#8738q 8 6:m ;;;3s>s?򉋼?t@ Ӎ 4AtAA>ApA%tB)4B14C5EfpC=C4HDADMT?;OtEYE]Ea4FetFiFmFq4GutGyG}GUP;tH/OHsj!:6!&BCQ}s).` ZH @)t)L+ qC2%JK_d&aS>'N/L1LHjט,gq1o11Maw /tS?"TPtTtRQQR_t(aL PSTT/M,7ג)$5R./StXWr$B-)Z]2-RSV!uB ΆX,G t),sWS$qZ92 Za-n_Xq/0!,V)F3##OZs5>aDWeLTK5Tr:&q!)+ H:+h)bU-)0B)l#߰qt*+gg=j%VJh^߁9^}y>YY9ޔ?xIMD9 ,kY]a>e~imq>u~y}>~艾>~陾>~ꩾ>~빾>~ɾ>~پK͡&@sSP 9P_?)(%?!B0_A^/ !,!#&'''((())("   ### # ###"""""!$%%%'((()+,/3445555553221 112211110&%,'')'&-%$0#"2 22122234'D'F'G'H'I'J'K'J'G(G(F(E(E(E(E(E(E*D*B*D(E#.K)3N,6O*8Q%Z?\CWILHE@@&86-2116,7;'>?#F>$LB&KK&HI*HI0GF6IE?LKGOMOQORQPSRRTVTTdVYp\\x`dp]]h\[[ZYYYZWX[CSc0Pk*Nl(Rr*Vw*X|._3g4m7s;z=>@?=8`2222222121212121>-[$dx!gs hr fo bg#bc&e`*dg1gn4jr5lw9u<{<~;:99:=?>>?>>>????@@AJXҵwѤО͞ʤũaVRRRRVc֔ҽ˻O H*\ȰÇ#JHŋ3jȱǏIɓ(S\ɲ˗0cʜI͛ɳϟ@ JѣHܹΦJJJիXjjӐ^!2JٳhӪ]6f؉P:l*ݻx˷I-(xaܷO58oqc#KL^(RaCzpbЅO=꒚-˞M.↡I.MkcL;t<3Aw89سkN6׺_΍8A";LXtǯW-=|k;?|(Tt셗YjEYz&WwW! %o6nh(~Mhx]H\ttWy3nwfJ#o%6]u+6P\T:&$Xxf$j1gcчcǙuUfQ)tr5֕ky5FyDxGߍgY*I%uVj饘*B!:G)fes^f%Yx䫙j뭸2jAjrݘ[K㮮z_Vk2T}ڧ2JJ})-+oZ4To9Q,QNӮ)LG,qQOlglWw #p$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL&:PH*ZliȢSp` H2a"6j|HG4Ʊx#>ю A2<"E:Ҏ1c#I5N$%3/nL'CQ<%%?I[CL%,kw섬v}|{-_?b!s =LD5p )<|D"c^q |^?aӟ>)< >no(WC O[|柢ЏO[ؗ{>Og_Oӏ燿O?~XGՇ ؀ǀ'}x·؁{*x,؂028W698׃>˗K|@߷L~ON7Th}Vx.ȅ^y˧yJ1dž4p8su~wy~K{{(pHnhlja?Lj\(A|`W|??88^|Hъ?hx h88ȋ{8HȌX䘎(؍X؎،(8Hؐ XȏX)xy yXXH(^ɑ.y*9&&Y,ْ;zH؄G ~xKIMOQɄSYI9HFy(cIUhgXkɖ[J(p0p`wyp9}U}i9 قYbؖ{} ف ٘ ؙi(_yɘY o))逹ـɃٛɗY h)@za 7)Dՠ-%zv){]Y[ ]I pyYYE]ѩy1Gyy "Tp ڡ ":$ 7@'~* j ZY   4xA 9:6p?*6p ϐ A:EzI:ŐY@R VZ*D*F_6 T 0PWkz mJaYceJqz! 7zPM   Чj: P{AP J@twvv`l GA`A :j*H z : ʊѬʫG |@jZ a F!j*J,ꮸ J 71 گ 0IK7  aګ ? j:"K6oŨѲ%۳F*6k,1E!4+++@jH+|3T;Jkg|VpH lzA! jmoK wz]˦l n @uKJ ^ $v`Ժ![V7A+J1 {NP |fwq"{ }>Z{G׼A{L WK K  盾;{|C 䫾˧ vЉzc'o {3  {q:k[IK <t;)p p$ ß@J̨8W¶B1N{Q_{ J,A\6\!/| YK A[TxZy*` { BANĀ |vV<AȆȃl|y},Q;<|ǚȜ Aq9 ˦ |Srz8Kˌ| p ^ g F8}qqZōaBoB{ ƠVrͧ͟*zBLܤϐ,< |+ψ\jΞ{{ 6Kͧs )k9ܧ, ,r9.ڢ%'],.02=$T4mA(z7@9| 8a~[ZYӔ@4[AV}+Y?]9_d}a]'էB@Rzo@6}9xz|~׀m3)3)P &l&跿ǫ@1ٜٗ 'd٦ږ٬ژىٹKRNtiʥa rD1ڥƭA} +lڔ =Mݮ-~}]m۽}m=]Mލ#M0A<ʭ lѨݑ=ߜcW^]?1ڤ]>n3XۧZ{$  H!{்:>818]"?&O~-:~A~۟K՛) [0NH)~ >h}^MwnAczd^x.'y.֑.([{˾a \舾 ~FAnNFٳm^)^-6 Nť{\,<뭰}U.nxnTH~-Nñ\ | Dag}]nĽ}ڰm'SN5ڲ31]_ި}ξ#lakj.؁PR?T_V𝎽zW?| b>Yfʞ:poYo?IhHv1t/v_pomrg?_O>Yp?_Suu7xTxz:/_w`WFL7uOvJMpIdpP`KEorZoMܔMg DN~[ >w 8qat 'lo6kMzMٴM%Wnip\QOnb'_r a,rEPαgv;x^7o;:=~RH{2少r8"AQ5Z$SN&ӠT fHC#9)HGҤ=hȩSax^V]GJ_BLh^.WOśWGP4ۨ@r4vh!a,vXN3̔ Q@ҧべ偙^ZG`2&gqʑLLui`Fޓ 1i& Y2eËO\bd=o1k6;lwSL꘦^7,35CDJ$!a돰 /̮0&xe _T>0V+21B>&( P.yM=P2.EF{,EJ ȯjrTi4z$˕<2 /+2̭rɁ MK=|s99I[hMhʏO$1$A .ls"B^P+E?$Hw#IOU4WFeLF (5<⎭B)eN>4UFS)l4R^_h: u 6 []}W}YhE#FU[SVp%78cmэ(ue]qͪܔEQ'`G%554$|pIM;ex\h)&Pv0ǯgtu\:ڈu )Pl?9+9NgR黢Fꥫ9$J gd1߰>!:n[mZVߞ[!N9@9_?أJ )r !No+5,?:%Ӡ!$YGW/uF B&kߗt<&Hc1x-<z{^i =wz٣؟]alW8ݿ}V+Wc0nEBۊH(S1lkSυ6T#O扭a3}G5ΌE;f4(*MG(:!< ϕnLI"Ԙ,T"NHSHCc f #R=BԤ8T#/LU:[D0zbV$W)5"o+]bWUtr)P`[.*jj,f3n'B-R}^c+~N܊m+=m[υnt;]V׺nv]v׻ox;^׼Eozջ^׽o|;_׾o~_׿9|p8<@`7p%pG>xpz/v @ƨ!p<GANn%7Qrw.Ad cF;ޡ ![7I]l{%@[M7t՗uGA&{d/x;!>rwj0$G8[lE}AaHp:!swLd7Z6zij<a܅3?#M+_g> (!NX䲗st,!kgG^,_>2ݟ`G'?>4s3ۿS??{@@@ ?@@@@0 3 @Ad@@d3A3! BdK %Խt<+@B!f>"Ӽ#C{>D$:ȢA˰D[Ŧkš{EEŠDİcL0脹ӄKx_[d63D8fTEd:h7iFj 7kFl:md)C9<  En4@:v|GxlZ\Gy,8{G|47}42z;~xCH,,H]* RJb 18  > !)M(]T+P ==IR.sһS S 3fEcT],|<@T!K[K24C"ӄ/VB!{[S 3/P"¯`+!ODU P$ _S"U(ջ@Gp$%/ŋMQWtENcUNJUJW/%⃀Ջ{SE0noeXr?bSW!O=z]ֈؼ()=W_| H~P/ 'U!` W=SKO=@Ն%Ə=`cμ75Nsԋ[uڥmZqٔڨ/dK6- ѽ֔ LS֛ Ϣ[LL̼ۿ%5EUeuLfS \^CXH%NS)(e]Nօ}4 (Mٵ2E0ޥ1v]%5EUeu<^^Lk 5v=-XP}WUaށ]xѵeT؃5alGs) R:wN]b|= c[cb-D0KݼԽXF5%S'~DaS@A +frS8ӐTU>[*?/ECcf}֛U!X]5HG>gPA=&Td\PU0HC/dZ8G(R9M-y-cVNHCVMefgbU(]PJ. plNډ<=PS%6YSld&cN}aC\-g}`\Q eK]\F k@]͋'UZmORSۯ-ع=h{ M6'mi;~ZiuMe[fԜN鸄j&665E붖]sSJk0^]![XkU^\J2FleK3N lvɦʶDю>m!. ^mnzІmnm!ێ zu7r'Wm^nv Ȓx X n В( v "ꤱ8XF#n pobrY/@Nh [ p A؄"J,,w,_,G,7 AS91xp`N(] _QqP)njj02 Ygon2m37(a>56OHBE:qs>oȂ@zX%0W)9˱K NLuPtAZtZRwuRGQtX7u\oXSWuKu\'Ubu`oueIeuJמtiIvtgvfdedȋ I8p`os tWK@Nȓvcupw;'uu~VW+J qªxxxBX"+b4$҉O#/:xy2y t,7rx3g3/y /*'h&p_F4h *qr*+(n'{y +"+eP5 Oe){ꪯR!( WG{W&{/B|b|Ԇ||ݲo|}_|Ѯ|BWӏ||xUS滨oLs8S4W~Ƭ|^GL ~~L ~' eW, m6ɍɗ,h „ 2lÈ'RHŌ7J#H 8e Bla˘2 œi#ʜ:ky'NBYjѤ*m)ԇKT(ӫNj?s>"_] v۷XjKצݻ2}RRoPM kG1g@…ENN*˘3j\| -OШ{ΙxG~홪s'=ɉ#ϭQCУg9s'&ӳ8aϏn}Q1;9H[7 r*`* AĠ'Q8P&a΅?T)X *آ衉%x)(=",X#0G8:䊶dMBiKFS^Y%D^j%TꨤFffmF_&s)w'oF@|z'7&qVgx9':8a" ٨)Vʩf馪v !u(QcuVyԯٰKQ~J_&ڴju!@Ukڸ_O0+Tp칛ի׵QYpk, <…)ЛT_t+԰`1&rs5@ ],ׅq$\4e3[8sW<%3M21]H'ULG4TPo%4 I=XJ}R\[5@{}x{6i6m6q=7u}7y7}7 >8~8+8;8K>9Y&Y#{9衋>:饛~:R#դ:>;~;7bN; ?<Ȯ@f<;髿>_s?:#'< 2| #( R 3 r C(&_0I C*0їg0`0'1[|x&rw|V$ 1c8Ẻ &>1&<83F.[0q'N"Jz.qBx hLg9$)|!S̠B<|M)ub3 &:qYuC1l`P% -bU&L=BΖ.EIS2;!)MVI:9͜b&مMen$`&r;}ꈒ^a0IdiN! zPoj ٓl6 }-A| Msg4Qӎ$N>ͪVձF9zY}&7Ӆ0Q}j|1ފisoNޜtIT$ 1>C&nt%@y^Sˍ'*U|R``x&(@O$N(OJr7 6I(.qVqtqYA 0= %0naq>1D5:k3x$/9'iVZ$Pl[h1>In 5Χn;ʆV`C3ю~4#-ISҖ43MsӞ4C-Q/_>5S <D`@P5c-"5skh 5-:6l\6-6-lSks6-q>7ӭu~7-y6o-ҢYZ{s5^9<A-83DTHF<t'ݭO+\Ujp2bg59E+tS(.'iV,f75a2S68i^t55tK>yvp`ҽ 5o& Q⁒U\_Ѷܾs]w7:n`wNwB/GoRԓ8VEQ:{؅<9yP(U#4֍#la4^#6f6n#7v7~#88#9"#:#::;#<ƣ<#=;=>#?#>#@@?AA$B.$CdC>$DƣBF$E>DV$FEf$GF tIcHHId:9K$$$MMdNML$IOQQd8!!8S#,ĂUc :J T H;&`;--9%\B p$;RUVj%Wz9F-,f;%[YjA;[Te:jA`feffbhJ%^Z%KneWj`;"fZ*&cbc#d9eZ&fj9rgoc\&pJ`Ze-#krfYlRb2mcnJ&oeech&pgcxnfhAvrBss# -aR':^gc>f]rgogqgg'q&hAzgR%{{:_~eXXNgblcDn֥-@AG䂪yxhhMj#X%Y煺\h;nnΥޥE~$dT6T)>Tw (:`Ra&fTv~' H&“ )F.)Kf9PԂcbZ>&rqc9pRA*jfrfhL>i):©b)nni;.&*F&9 iiRAvj<9icj*p**|#*:})橎*N& &ihOTZ9Z)r^k;w)~癢=~cB&kr(> B#+kž#f*Bd!!(2+0iz%%K#Ş#,d#k#Zʪf;J+:Wrb̦%ҥf$N+K6/f-h:*.Z'c^Bcv(\,vfʯqگɞU'&螯~/iw%үR#>PBkY0"p].Fp2,/r{0&;do;60 &0R&[%ppS@;vZjW墫 ž-R<%s2Kj.S1߭o1@3>=?wg?S4EsDg>ot?G,i+ٞc7UA\1"r;bE4#l:ib沋r\& u6qQnp;JRg*o-.+r-wJ5u;jU#V3ulu5w9׮qMB5YSu;;59ǵ] 5^;Z%``4\\SA+Ws&I6,cCuN5B_.4Rduf>u_mL?&~!4@wRi-*hA6.734,Ss11w Wi27͞&4zB5#,&qjw;rwWokgywzw:-n卾hqvC n}77~xgo9zf?j*g#u0*&{7Vj83ghk{FRp/iƩr?xw[gÎ)CAV6ã(ڵb5`i^* r96osD%:>^^erʚ5gy;s3gTV%l[eghp(Z.#Ve ygf&fF*>&aB˹:3#:].ohhr,Atf咽VgS'SC3z:Gx$kz/Ҷ'] {;f"&O.䳣 :ʹ:Ί{:i&lCknUB{#,\޹F~<$O{@:Z~l ==?+3};C=K}S==#}ks{׃}ʋ=H}ٛ}֣:^ۿG =׽====>}'>7~/G+W=_O~o3~+>}>>c~>þ~ݯ>~>~#۳8ǽ~7??[O?[W?;o?{8,w>?Ͽ?G?@8`A&Ta F8:CCA9H'Qz,eK+]ƔrfM aY3N-{j2P8)&hPƍ$FYTjՃTfUU]~{TءeDVRLr4A$q{޺w /͛࿊ fqc'Gٱa͖f挸2eϢKs:75jϝ%Y6ӭqvo³ؾiL\zsk/ṇ{ju?^xѯ^|V}=᪛;e!PB0H#k mڐÙ<0EDNOZEPF 0ĉz(uiR-!$r)I\|FVs'--/ӥ&$L3D!5I93I;sM1D?CbM4%=DHE{dTGG_EIS-Aݧ!KKTDO?CQ3$RCQ25-U4MQm1VfVX+W o WUBڵ+cB6+eQ FgPiaڄUq[nE۰½\ƍ*#Gʁ:h)܇0wzm1'n4( *"xb.>-M b+7c&IV\  ˒qV9{[2ZhOϊNZive:LI2./k Zk.F6{7l;6nNOo;V#:|l+HrCzσ.;!r3?es;7cqǜvw('og]xY > 9ǜy\dy%oDYg8oq`]rd- N?"m7ίc[ʴG>Q~J x+ZAh5%Cy}$Se^# t aCΐ5 qC=D!E4D%.MtE)NUE-nH).e4јF5mt#'uG=}GA4!HE02"!IIN%ذ9b'AJQQj)QJU G-qK4v`b/La41Le.t3MiNմ5Mmn7Nq49('H1vΓ|,VDx&ZB)m& "t'%~ H&"h&0q1c=QE>x :HvR% Ӡ5hDiӤa4O6@ ].rn~diDPAL"@dbZVpN\OVTȃFX,a  V$k+ex:8NjFw\ھ@=caq&HYO.E@zkfq? "Ртjn%b CvFӵn b@íKA+:UXo\Rw"+j#2Z5$T~ (B׉b`o NX)pz޼B"w()@:q \-Qb#3HNkшUOQE)ϟA 1?M{ڑMv:d)OU򕱜e-o]f1e6ьCTig9?"g ֹ#z 5?pqhEGϋvKHOg~d*iM{YAjQԥ6QjUխvakYڊPHH:Npĕd铟1- &e%Fki*nIޢkkoQoek8nvR[T pj!6}@5oiMcGD)iN99?mGWJJe*D*?PMM_`qkx!~j#܊IyD$ZUaн]G:k7}"O,|Ou+D _73} !/CODo:(i[nuv;?Ef`IWD<ŬD˔C 6Pg^mO$a+[޶}nv;A tg?y+A$>aGwOv bOB&f˽.>4#`Dn L+k.'.0hȎ 'AVo"Έj/}.P/nf| Zj,l&*(*nxtnR"밨2 jdrP4z"V>0"Hff V/"- ƊmȊ,"t ȂVw1Xl^@PƄPwlMXL: WІxMQJqpNaьZQbmfQ oqyщr11""{ ࠱ Kq+1{ı.mx 1qL`  2!r!!!!2"!%"-"1")2#9#=R"7#Er$E2$I$Q$Sr%Y!M%ar%_2&ir$g&q$'"o2'}"{'!r(2!(r))R(2*}r*'*S  *,[,R&)ղ-O-&Fj! r.0r _ r.20j0 L ` +M:0 S*K4O*SS5W+[5 L!`0۠ 27{7R._(c s!r97M`7(:R3-2> ?M@?R,w2;Z;Mh ٳC1DRDKDKD=r#k@?SFFTCCE!9%/Fq o;;Cu4i r/aKgJA"JK RL#"LL4M!MCʹMtLKMLNTMTMtNUO05Q OM@QߴLuPUNP!+RuRTR!5OO#uTAPT-TETSU?5U5UNQuVUQoVcWauUeSwP7>T/% 4FGF 2SyX}UXiX3tJKtKPE[4\Z_[MG r ]#H o]uX Z It3ACA`;aTaۓaaѳX5"a SAMb.!<b)H Rc @I3s' ;3 ESf;cǓffg_Sb+d3MVh Sg["9LDI7aAt+r,CTf1iWlSlOmKSm "2f8ڵd0h6#7|,921mbV0nlk,9"~Ftyt Fgٝ9prp9ٛSǔS}*Ŋ!Ȥ35X > ˷ y zGG{N [Edo b  p|&]X@ڨW3YKW  h 򰈡GĬsخ~ZZ.'Śگ~; ۃL"LJ;!Y%)+8E{I J{Y]a;e{imq;u{y};{;{;{;{;{ɻ_;aN!6A;|A;A<|! ;!<d:!-܊ .ã ( !A,ŻʻøuYRRRTSUTWY\M|@y9t5o6p5r4u3E222222222222222222Q4>???@@CEGHHFCCGKLNHA<98{9v8s4lw(is!hs gq gr$ep+dn0_i0W_+SY'WS(]P*_P-bR5jUCoWOvVTX^Zf]l_makr_ef]`\Z[ZZZYYYYYYYYYWSXSOUQOSQQSQPRPPONNILHEIC=G>7HA0L@*HE%EF BF =A 7;!59 47"04#-/(/.-,+3(*;&(?#(B(C(E(F'F'I(H )E&,E#*E-E-E1G 8M$;Q%AY$Ca(Hg(Mm'Qs(Tr&Uk"UbOPGF?@8=4:,2"''%$)&"+%".$"0!!1222221-(%"""####$%)135556530 , ) %       ''''''''''''''H*\ȰÇ#JHŋ3jȱǏ Cܨbɓ(S\ɲ˗0cʜI͛4K|XRΟ@ JѣH*ӦO*|>tիXjʵׯ BzUC6aձKݻxuɶ ڈ F}jaa|۰‰&&XT'̹ϠCTMSa~]Åo%ъm{1om,ȓ+4pߵm DMCzo<}T%'V;Į9i˟O+}J>V 4[:\i}aubFvQdU7tpE߇ (鶞tQl oz' m*k8xZauMaxl$&L6yՋ3ێQ)&e7a{V`;:,:p)Hr pmX]i9_=E_h&x wsF*餔RnYfT˜Pq_wȩ`yugAZ*k}PgFxm tѝzdk*Tv&z꠫Xx*ԗoTm+z騟NlHݒƋ߷(ZԮ,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ "?GL&:Pl"H*ZT".BQ^ 1_D2ht#F9_L>F~ dI,{D"E #$#IRRaxr.HK<S8|c):򑷜#D|q?,0\ޑ"2ie2Ӑe49MTVӚ#b`!̦)NFӕg:չNmӝ$c<O\ $<y6J@JЂ 1BP.m(D'sgF-z'Bր7Q 4V*L#,mKUR1q)NbST : ANqStCi:PEMQJTNuH=jTTr=MSjU6_=ZUNBVVuv+Zjִj-r^Wt%l^{V2֯W#kXb5Ua/˖>ZCYVlXIӠv-kaZӻv==fSV:v4]kk+V_uf!i0͍Ӻn:{Nӻo6{Msi%KNӽф3L&Ӿ2{E"e'?:D04F7 %a ¢ τ1|aѿq-E,KĭD=\TO9cR7d#cGz094"E&rX##ɰd1b^ c(ʄ2.@vYKfHfi\Hӌ5@40?-" 3@ I \lQyA5" #Tyh<# J0"hB*PtQ@P-Xzҕ¥3t9t=g|(GJ;q DY8VEƅFSyA|A/@T`K8ua 7Bn\6l| lw;1 }@.Hl]A0uqH>A:{:ؕ>fZoAA e~r\{;&>]DJpw7~O{>A/3 z-! 5o_rOVzV"0`kavk{Gs-60pk!}Fr HhD8kayfPDXD 0%XD'(ky~Y!5P7p2};rxJA 8E eFŀE Z!FT 6 F(4,'qgW6`tG0B!|AwNlBg}rHxFsч1ngxxzA@Eayqu zr!{[uP%osWqH gt5uA6PG ?~f}^Wq!wsZHD!i}؍8H8x8}7`t6 '1p'x(Gw h` J8x6Pwuo Q!l`QnhiwS|6P*"$I6{,iYn3YAђ8ٍ+p<>@9|H ~aostV)0)lf7fmgoeVvyxz|ٗ T ~iB~maP$t")BI#TAI]D)pAJXx9YyY=t2 `y3xkak9&pj)1i3y/Gs@'tasF ȕSրi8(Hyry[ם0TAF`a_^|lǃ~)Q 11#Iq\2Å\s!e\%S:  \"Z[~rǂ<؄)RHT WH Ɇ)3xꙏ(z$0I7hXP ӛAFz/sTxyOfjlڦn@!Aac DڂZt$F8ZFE Faw"Q.vjh|b:WA`њd(F񪶪Sb D'Jx4a*6)ЊJVEZV:IjDaj:q늅*&z"k᪯J8zZ`PX믷*6eⱮ;!P۬:KP˨ZߚiJ*JPPa:;P35+7KAj=9;*@k:iZE$ " ;,:_ #KZe+g?ښ#˰bcȪa6XZӪ:]۷j۶;!y[m[aˆ;ڸ[ 5t*tkOZrkz*ĸz';r[Y!zsuyp:[{X ڻܻAн;[{+ߛ۾;[+ <ۿ|l tY<\"  !<\'\,,.),4̽1\Q Kps YpkYfK Pi_ 90h8н5 g0PP@;]NLVj/@ \ ;<Og P,hWkhn6R@PvQlSUjƁ,pkJ XJȍ Ǚǡ6ɔ ^ ݉ߵlJ o} Nkڭ|Mk <:\ Qė,^lS`=E>X\Q@ p>rνts^!yn{фcޅ,k~ 3}耎|~^问n遼k㛞Ę.鑞mf.lN.Anj~0oĮM}n?޽8pN^>NĘ>PmνH Üx^^.o~ eڮ܎ ;p#/خ޾Jm}\Rξ8[ N9َ={LݍNSP8 b~@|m&Ο=Lčlk NO݋_1`kY} w?޼ܣ̬ ʽLāOO4L`/ޮ_nhjomdxj}ƅ Dxe@C 3՘XF=~)0H3 vŤ)>QEQ!miqdBP=$hB7Ԙ)D9>82FA z0Ϣ-3”XAxZ%DՁr]h"RLkŋS*յWCTW8TE|soU  K8dpT#ЕUJ8hxf\g@aFDn޽O1˙,~\_cҭlu w/:xɟ׉^=Dw4||?w/:d407ڃ/Atp4#LlB B2p[8QCqE\de$m,EKG2H!$H#D2I%dI'2J)J+2K-K/3L1$L3D3M5d7zl3N9\@lǓ:94PA%PCUME\7EkTEAR&陣IOJ-5TQG%TSO=O#OGWuSTg-M;5W]wW_-I?QTX`G] Xq]6ZiZkdXWtX5ZB}@b7]ue]j Ay%axx筗{~ݕ3 n5`fa܈#NYeN3 4@b?9dGl}oI3;x7 efoƙI8\9g&hnifi:jj:kk;l&lF: š `ӦnÜ$%'n/\Peln膻%5 Ǘ yq/J-M.Jf}3 L1E7URQ=6?2vۋ 3zۻa6Q<U\Wt`6z}uzz+toMoԼؽD3>ݷAjbx!܄ %Iu@ X(R 48>R)wfxLgC &MԀxS*6l7vpuCn.H \G<ݼtʫ81ax7 pBuZ[=,`Pb(A>:!7e[cG>яd 9HBҐDd"I{d$%)IGNҒ#+INvd(E=PҔl*UTҕ,+eIY֒@j-uiXҗ;e0Ka1YKe.St+MQimoƞI)j.s݄%8MqV$9;Nuf;H\fMuÛy23*ЁJ=*P-s[<}:òE[QsrdF=*RT&5lXyiHc*ә~6)&QН2 Utk\-=)L=ân$Ԕ={L3/20zBw##p >Xk 3LӴ8H z< ꜣ3t5D4ϐ)TdtLA`Ot)DW;@ D3 l9P A9AO@kAnP:\6P{A5ñQC&TqPCmUB)AѰCCт#U%R=?$Q$ %A'EEy!%R5¤Bz6R S3 Ӳq-PC33CRS%6a7-S8CS%0QTE$mԪ9шU;,5C"S'+5,c=`CbMEL̈́ L^M(]exeX-h6fʽ\f])\[f\^A0v9eY#ekVkf_fl~ZKrv]Lq~S@eڬ5ށ B[3OȂg8ZÈ[ 9WPWvlnVgn~艞 ư٘ f``cوذ^PSP]jgjM^k]ձ6+)< 0V=QWP=kyP?CNթIPYMk XJ%4@WkIџ2!EEly"B@&+QAT?Ԏ'Ά9@MTlkVlu#ĦR&d첡 p4͗2}*ma 6e .n-tOI9v?nTMSUL݌lC@æRvN]nlŎo1NJSU Yنо7GWgw';ӍpqE2qCzN'r@Ҙl8"WA e2KG0@%MO&L'Nt[*uJtqɅ3d13bDi&Μ:w'РB-j(ҜW H$Bʖ/sPˤD89ؠN4H Ya]U,޼z}jaAD5,ؽx3X:jS (Dr‘ c9X3>+.m4ԪWnz)*UJZPcH=Nu͸< vكp+tЁS0N;?0.<.l*\ t_iNo> ;rSXTqW| էs5\As ځ ^ԑm癆1CB=DeP<+"L=c1 2"Ey$Eq2.)`yYV{s809@a6@_B@oDZ9ֱ# =eIٓ+eM  Y9gvf@LawP_RƈUC·Ղ ޹)z)Fe5%zW11ATB^[hB)ej*\@U%AlUi6V j-TNu*CuT{Ww V1Ҟ+o݂]G.{>7/W/Ys\ *e$AaSMYAcXal/{1RWLq)ez ]B[n}#VqJ1wڬs0IIġ?tIB=5U[-IZ-IW{5a=6e}6i6m6q=7u}7y7}7 >8' @8K>9~h@=AX` {9g9詫:D:斻>;픗~{<:~{kpPa#{[>$c???(< 2| #( R ޴p.P| C(«I- - yАT@H6n;X(ĎBAqIp H6p.7 )LqUbZ_q fUbj6F D`/(H֦4EZЌ~4#-iH;zҖ4]LsӜ޴C-jMԦ>4ORzծt_-kMDѱsYзswa}6 c6l^3{ɎMmms~6NP? .$10 -DXwpAQbt.VQ B?D npDZfw oyӻϷ8CPA~DA+| $~ Bâx{ -y] Xx{h8`q/tg|.Sn0ins_}xν}u :*gY:󙷢~;^{6ѾqI$ lo{ Yk< j0|N>xvBu/M_zH~|w9wG.p'n}//tM璯ᬮ5%(gJ{}!tPCТP(D\b4"P!.`y9$Ÿ#^_. Z,\],BB B+̥ şR`Y-z ! ` "a "_HŸʛZb%! `9`^+t]IZa!5Yf  ` Ba^!a$b$ȡ$x_ݪ"`!ɜ, b BZ."^-$` MzN!!1YЭߟ٢%4""2"9#AbQ5q 0+ " £,c+6-آ8,.:f:b0;=2!==cc!=c@AA&;&-d<:!^H6$"E6EFYKjd5$GcDJ$>> "cLcn!%^;v'$ֵͣ>JI4LvJ!S"`B #%1"[*#4.bR) YZS&͚e/ΡX%`B͜+Qݣϱ)Y_FdZe*fff]g~f"9.[o&jjd:&e"ef=2٦g.Ho&L&^^gfIqYrh:'n2'ᣠz'{TNofcBp^Z_埡:d['Un)g \W`L*.'q~%\1߆&L&&v.c#Q~[^䁌A-((he߈ gx4(抒hfyz] <&s&i4')~*1`#h”Vinnr"Xc:ifiQ)t.f&"}i5(ki) &mFBd]"'ghhzh語ꨖj*^gvN~2**R+ k.*+F\U^1JhYnZ2k&e1r\+2c89z!)m/B[Ac3+v&__Ⴟ› J$IJ",Hq1O_cbqk^52a1EBA^!q1"P#{$B%Wq +GqaZe&+3r303 t3AgAsBӚMBC+tDSDStEE/A/F'tG=ZFkH;tIItJJtKGtL3NăM4Nt4OO4PP5QN5R'R/uR5S?5TGuQ;TWUW5U_VoTsW5Pg5XW5YU5ZTP5[5Rõ\uPӵ]5O^u5`u`6N5aǵaZ'bsuM!dtc6c_6Xgfug5gVhui5ivTjMC-NBe4kR6n7n/von[ 7Y7qqovr[C!vmr;tSwuucwvvcuw{wvx7ustxCqwzow{g{_w|W|Ow}/}vMy4m77(㴁+88'xO7DWxPo;mǃsxwc8[xǸ__xC8ϸ89ﴍ9t'7y 8S_ygcyo8w9W9999y/::9/K7~?Gw~w wvwz?wvMlo:zznz:o\:Qji {cD6!āǁ 5vzK{#w۶[kkv_ut+vvs[;{;a`;f绾;uM t'_;>:>W>>>>׾O~ ~F>;?':K$B=o=ދ޷w}6C9=?ۻ}?{e,;@ĩ/^&TaC!FhPbE1f]UQU^ŪS֙? kW1͞mVJmQ{sԺJ޵/ռz5Qཆ"YX15?nYr`Vy-͞SR]pt%EN2fǧMŽ-jζ5㮬[2ǾvG#Nnx怟/1Y>:X8An||M?w=m߫/zx3]?d*,</3(BO%j) . 3%#!0`΍62qDOFB*s1!R%l!n /o<''DhJ,2˃t20<4ѴML.4s8K<N?䓴@2QEuAUmQ3dDsU/AU5$RXOUY]6fSbXZ k%iqWm!uVdeZU6Ut}x%m)L }8`!"8ND")$?B$B\&!k9G"/$.&%;dqdKTfsg6y3&i~燔vkhNjh>9lml6l"n[[\ /O\o!\)oEfY9s׼I/QO]f0`GQ$G~ EℕUBwY/ms]FfG^WĜ'sBYeQ/I 1^/iރ&qxfGɜ4 ,l}OƑ #p< (x" .|"ABp[_O;ENy=Y,Qh'OfXu1l ,,!(A.Mt;wBoX0P9D(i8/BtU$ ?.ы+GdG=-;had,texċYd<GUQ!ca0byd#ҍQ$QJUR B Pd>[ꐕs~E\'m4){Le.d_1Ǔ?eO R`37?@s`IC$`EeJQt#PGp\BDHC `JCC9R;-shOD=M%5IE95-uKaSΔ5MqS=OTE5QT.G 8xUUzRn$]WG%B%e5Y: H[ZBoq=WTڮ*5axx{uc!WNb!> n*eA+r/iQڢuka TN6ma:▷m3u[Žcp k\. DnaNc qpC9z[ўT'`E=_͒aHCk?AzCHa %;eY{"E}[`h`(&;_5"--BaDP 0 2]OOO*$;{b[%ό<%tq\"9 9@MܓZ AhO\)*~iH 8PR3?/hAvS9aA+rX1;s5C!Rf 7"2A&T6Y GjϡIh]|}\A^GAVR'eX"#}b7$+aye$e1Cku+WV>2Xian>A 3=)*f BA&ss`&ѽn/vS'A͞,\; qZB6N'{4'm~sx_7^Mzyчo:aXR豤9 QwT2%sﻭw`aɑbyُ!/Irury\ueQِp` ^G =wDd&aFp?~KQq`a}Z0ҠA/hC!q#pmѠWaRp!wq͂ aǑc0  !Q!  I0]1IFHa3r#;R!K/#5r825Ha axH$G2('$wÀ"Y$K#YA&Gt+a\MrMR// 30e010Q12S00 22 339S0%S'%345&-/13GsD33Kq5Q4736)s6[35a6Y1A4i31OS7k4ws/1s951}S86S67y:#96wP")2/i fQ .I,-˳*M09:'99Q.w8S:U;s:ݳ>s=Q0,S;Yu`#G/pj `| vcӇ6.6(>'6_/ca6a[6j64V ;jdIa!gK6f7ȧ_^Co^C_/Q]h3jVk^֨ȊX6mlmA6gqb,h!mP$fUn W|(inno hg__`p57&r6igs*s7tUoM7uUwuYu]ua7vewvivmoζB'uowIG`vrgwz翄Gy y7zEM`zSG¢`h*訢|,{W|X^"mɚ}'.}aV>u|⌊M|u X' $F9 L T.  IW''h4 as7 dh޼ފJW8e.bx虶7.?Nv' !tjb8%Gx' IÚNx XxrWy8#@8DikFlȦƐ lFF5hN ja⠭F;=h?/?$wakZy&fߘ_N $_.\9{ّek<%j[75x` YО99Ц/iY@Y hl]%ZeW%_Yy\HY(\♞^\_E<v^^ ڟڡ)+yye9y-%5ڢO/Eee^69Wwe]VbzuAKڦyڥzŘECvy6`YXbnyZA(:Aں6v>B>0:ڛ<<Қ:x [YٗfF{+{<:Cs';ۯ/{:2=B{4۫#WDFA>xis؉nrv[zm .Ijv*q HοXhs쬆{a,l{7 ;y9G񫼇@׋y[{{~Wtr! yyg{D N {yb(t'h@Mȍuab†qB]7׀m }݄A)G!Rm}ٺe/WC=H ~2i DVE)<H)g)L/)Wscu'Tsnσ -y\m| HܘV'I +\)Ikx0(D%ȧ 쯧×>q*.1Nn)~w<,~^ޚX<,8~"Í'&;`g҅7 <5 cvf_gnrn*v{bk_kà^_骪wnyGʷΎ~柄߳" <0… :|1ĉ+Z1ƍ;z2ȑ$K<2ʕ,[| 3̙4kڼ3Ν<{ 4СDԨҠҩTZ5VH&Wa݊a-BIxK 7ܹtE+Xf 8ػpv5dŌ;~r؁˛?5digVj}mveUanaYa"Hb&b*b.c2Hc6ވc:c>x^bFLi$,4O +BPW&e^~ fbIfffjfn grIgvމgzg~ hJhh.h> iNJi^ini~ jJjjj kJkފkk lK,=.lQ ^ha-f nQq@~+n znZE΋oB-`ʻ 0Ocrn,$D!,!!"%'''''((())**($     !"###### $ % ' ,3555420-*'&&%%"  #').112112222222 211!!,&)#),',-+--,-+0(+4#*;)C(E(E(E(E(E(F(G'I(F+D8D@C;A5E"8M$?DDI HO MT QX"W[#b[&ae$cm"gq hr hr#hr+hr&fp&`e<[ZSZYYYYYXXYVRVUOSSLQQPRPPRPPSIKS8I[*Fa%Dc&Ge'Kg&Pj'Tp)Wq)ek*[0L12121212222222222222@4\7m:{=;;:<=@JYfp|qvskp^n[n\n[lYc}Y_|Z^~`gyeoai}FmAq>vB|DDHNPQQT_}K@@@@@@ABCCCS H*\ȰÇ#JHŋ3jȱǏ Caɓ(S\ɲ˗0cʜIMn>,sg*= JѣH*]ʔⴜ K9fUfmʵׯ`ÊKAP^1ĭ"&u`֒O˷߿ v!ڄW-(iΧ Q.etϠCMtƞLv`Śzxr빬b݊Umȓ+V܅S}[ 9;gS9|pWg˟O~{wm3݅]s1ynuvmwZi'}fj8Ujug"wFj^k9Dpa<@r\ BfX3"IoI&cyԋ.~)aHYݕIǝAih鑑YJXDlmS>A5bpuɠd9ٍٔtl&ՠĩ裐F cx%D`>idFgf♪Bf䐧7QwBhkyI+k~x2;Z '.]vQ5-*8.I.:gTjメ ,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"HL"0%HR'2aEBQ`Ģ|H2e F5i4 稁.JDc|trHA iHBr~LY YHJ>ԁ"1YIN^ґ#D .zFOB&d`,gIZ̥.wC2&ǗqɲZa0Ci! b(σ@@,);Ή~eR|&}9τt GRVTԦIɔs䌧XGPs phR\j_Ϻɉh-n$3fyIrNם? `/)23̓-[t2oDOx>'"qq*qs\8C.^xiȃ]N8ϹwN5@y*NHOҗtPԧSXzӭ{\ǾuH;nv^w;,]@-O}~ _C/|O< /7z3vpx0x`s? \dx,~c/2 ep睾GDy@z&d&haDz&O^=|տ>5sG??k@4~aA{|w{tw~Է~HHt s (O~tFg} hEg s |syvg G7}0g~> ׁk @ >tkzk Ѓ?UY(A Z?GϠ pUs`X|thtfshm膧W| z]Xx>dtwy {ȇHx.Haxs؈ȇV ` \XqhcXx }V w? e> J}HzXtAG -@ >G 3Xxyk` sZA' DW؋DŽNs oE\;HtsͰ HXxtuXtxzXhl?zȍk0HxאD(ȏxٍ{xPHwȑ*yt,ID.Ȏ>g v6hZ}8t5sمBԈԈJ7`9QypTVY|[ t(yȁ  x9 tyٹɝ oqЩl)AGkPy ٟ 韂tutyيI{Jx)I Jؠ)ʟ&扡>w nI('J*zʢȡ Ok h9ɣ:ڣ {DZFIJ t i٤SfCkmʟmԝP {gzo(Wj?[:Xt x*jL B T؅@@ yD hYYXt򙋝ڊk@vw i tJ@Ia)FWdz pE*P@29)j? (c:EvGW}"tH x @ Àtzp( <ȏD LʭֺY7 s̰͊c(د)Ns+;畩ʅ kв"ʡZt?K秳CK\Ш@FipgG' :|R ׇOP^}J v;Y kݧ{ys8rOGx.}Dx; ;yqӀKrʉos [{yitͅuۺKzɶɺx1 xXvV\j;O dkwJ7x׼RW uIt廚vKvK_7x+xw vu;AjSW˄ݫ , Legw š+g'٧E +Ks8úo7 EWp:? cG\$C ?FM NK@ rE WtGnk\jb]KP G3w,`c6I _o\n`yndQ@7 }@éV5ȊȌȎȐBtkb\ɖ|ɘKɜmɠʙLɢ\ʦMʪFʧLʮ˘ ˲\_L˶ԶNZ˾|˿s\̾|Ȝʼ̵ͭ<`iĠ\Φ<,|ΠΛ<`GE ktP EF P,ACd4asTY!}F) TlFy|B]ԣD a-L֗,N$%O\REao$]fF^5fQA Fy\njTF,@:؎Gؕ6mؙJ}ْ}ٟ-Mچ4َS]H-ǞMFmڱ 4ڴ}ڠMٲ۷ql``4ۥ۰ǽܸmھٮ} ݛܨ-֝׽۩ٿIĭ܍ŭ}Ɲ޵mG&7l=]MݬM0P@Q\L]bN bN$fF޶} xL`D }]T-/1~35^G]\Lede `&+>ǼZT7OQnSNU.WY>YdhAҟҠEnk1ZD&N-L~mu罬}tapmF6 R FhiM],Ld闎6[$韮誎N>{^n~뭾lʾ~? `~?>f>?7f #zLJ?e,>^~"n o5gO%P,Z (\6Ol P\hhՆ60 T a}l@}0^`܀⏾nFeLT1eU 9U?;T=TQU5^WUWW)XATeUDY \6aqAeXVFtXu {S~&1AeaU{OmTsoMFl]Q< e_a%`$K]z]Fi*&gy?oܒ{אQaS[wo1 KN9g~g+>bHmmtо{u& }Lj9 6-ן{ ҿ{B7<AK@21D)S[4x H<[^9 c"rNfGFŰ@7$4@^a;@+Kca 61g gX?&P3Ìfwσ?s`aȠ5A39X$eD6';PִF AWo3G#y$i/@pd]vGN)$6IRҔDe*UJVҕe,e9KZҖ%"җ%/ dD*y"d6әJULjVS[Ƞ4Mnf79NOD$g:I`3g<9OzӞg>O~ӟ:P4EhBP6ԡBQVԢD1Qv4hHERԤ'(JUR/UKa:Sʔ7(MjS]Osԗ65AJ ێZըSaiIzְ>ucmkZ׸~W ط6Ol_^N[ڿVv mm?{Ύ6_kFnb~M:Әw={Ömn['β^ xq?u=pbW8Hp,oXdDm<?v{};#T!u8kZ6׸/d/bN~:OK. G\g&|:Ab8*vXs^kM5#ѳm=u^?0⍫n=hw1dv8&Cy18Ƨmk{&ekx.9鵼[iUU|y2>-w+|7Ko( ~^ͷa 3_2rZS6 m{JP#EMD#yJ~(Ɏ? @l)?)L))l@\0l) @ 0 \*@ ,(+1@ A$+ <@:@=LsD+l-|A ' $TBnT Z)B&*-&%1$ Ay2d6d79_:=L@6@A$B4CDDTEdFtD.ɗHDHJKLM NOPIR4SDRDUdPTVXWZEZ\lE\^4E^`ĜFa4ScTT\fdthhliFek4l`nŚHC;$ttEvutpkydzGT|E~FЀ_s"\}DHPTȅĆtMȈEFʼnHf]H lĎlGEDIt\IylIXTɗɖɕIGo,E J\ś4IHȞH,IG|H^HJI_ȦN4  K<˂(˩xKHKKlK,L˻K˿˸̿dKdLD̽̾LL|LlLŴLnjL̸͜ϼL\LT$LTMM |L\|OPQ~,|"Se3DE'Tur)|Z]+ \] +ܡy^V$ACcdUeefughijkťQDVATH_ab dQfq#:ykɖm-0肂`IwI 9"~5ڙ#Q QXZ9'(0ɠٝ B1%IZm[U0Jܹ%Õ $I\ a\Ue} ٩]0 Eޕ̥եIX5˥yZ䱏V%]]U `uoiw]09Fɝoy }܅y^y-R  Ј\ [`FYۚHa80^ !^Z "P X"P  aF & "~, Mb%"Z YUb0 Ԡ ?Js`G=zc!6W5Fj7`ㄵ =Hh-.@ ȏ(YQ9ޓo]LMNOPQ&R6e-j1jrik^p쫾jwT+xvkx^BNfƖƖkfAUMj.~jvlƖ~D2&]SMNވv h a~aiNt*._xwpowx_`onoooo6v.&oQt_p `psp n_8ή]gdGq~  o oNl.ophݾh~vpPTȆll>nn6m&&Վ$l ׆sq9mFnk~frx)(6s`FrHk%g&2g Nn7x6VypW&:_mRGu.C (l1jsOύ6k`pXkHStf!wuUOi?Zgg,mnopq'U&rWw\a&:uwa*&{ߐs?V{W ᐀'xGxw̐x xIx ax4אs'V%Wgw',Oz7G?Wwzzzz7zzAAPyy{{{,,/|ßW|Goŏ{70=y=,CH711{@y--y:OׂO؄/=y}>HO??}CHC؟ڧy2ؿy/1~y98y', Ayկw~}گyP>ib` Pĉ#砖%Rx ŋSes(Q"BKCYyΧOu>~HB 8TqeGY2jXQq4R1-_ WYp(‡C&D;)Fqz4U#Uz/#KLRK_ rkΛ9#ܳaP17$3IʭtD B9(PMZ7 cz- M0v轩/n=/W3A] *p@  {pN9s0vcG,{fIߐ}Хg C:joADy{z5Hf!HrNQF>w '~p߅7!| Q$a0JTj"HqX^+"6>8:HAum8>Rbb`N`fΕ&q}Yc~iǝw觅61T=Cɹ:ZfKM%)OΨaFTi$gz ڕE$q*qDaPj| (f+Qiafa&yTz,>Wz+c6)ѰJdIl&GRKDm"ׁǹ(KRGY+Do܊^;nR'%,lUnLkSJJϨ]^qMrǖDpFoQse_'7$FЀ7Ue;[Cgw6*MD $-L6$@P5awĈppӑW,ih:!Fʜв1Kd&}薿&cR8+6`k1<Gh(fS8(:qq7|H9|2ІAd9O-BKy2!xDp4Z2Ce/ LPr%1M`Rnc$(BA!B1.т%,as,T9b.5l:6đV7(ܔt5-LZa>W&*)QP&hj[zԖM*bl(*Rڽ {WGzULjQQ`+tu%`&z.B>b*ܮŢ4R@RUVa=XC.6d5N,|0=$Q-י\{RIA ?϶62i}v3JDAkOb%JDnY@6z |;Wu]-r_{6Cϫ[A#.wQ[\bfy/詵Aot;^Ԥ}c#ݠbvnHj@]!C c*k FP (YN1\0\k`0ƅvqDǺZ7CsDt,@^ '‰ h7,K.6EИZMÓPLّ-åJSYaH&Ϝ59o.ܣ1WXY CA5Kʢ)"1:.S-Tyϐu$n.CPª8RSNnI dYMQ>j Cቮ: Cc_s&r_Zro+7now#_z{w}Ypp/ wgo8CfDZry%њ[>+_-)45\=oF/z*3N:ԣ.u'}V:ֳ~k^Թ}b/;vN?v˽dCz?]? ;@??K|/y[z(:#}׼ݎϝ_ߝ=B}Ϻu[zOu}] {>}Տ/џw?~3PB%otg?` `!)`_&&RN U]f vPz b BB ` r` ` ~  ` "a 6.&aa&!Y!Ba!^fan!:`! a !! >aa v!!""6"$!2$"%#^$"n!f"'bb~(:+*,_--^..^/Ab#!c)19Ţ,JQc]%7%Ƞ5j4V8J^9^8c:":b92;ZU_;^==J]<^>c?J?]@@<]A6>B^Bj_C&CBdDD_E_Cڟ@^$F^BGNGvdE$deA*H$LrKf^LzCj$AdMLDΤJ:M$RRR$Qf[6 <7E2%V:eRfeD ye܁e]%Xe T0dWMeFe>ZeAemV6[e@:`8f\ b*\j&bAe&e>%_a@"Z)"(`q`jfjJjΡh!i$fmb!'(lf"l&pmz"op>'qRq"'&&bv^'qwfxfxeBi'xfg{rgyyj{xg('~g*blp6oVg"F~gZp*: U%W6f%cbce0%eb^hB%@hfhj_Et#7%%.`#N"dhh㎎e㏲ih*iQ.]ݓ薶cNcB@)v:~i5i,2(Ʃ)֩)橞))**&.*6>*FN*V^*fn*vj@*pc%***"jrĪ*檮**++&.랎%0N+V^+f+:@Z֪~++zf%++~hƫ+櫾p,,&.,6>,FN,V^,fn,v~,ȆȎ,ɖɞ,ʦʪj xJ1+֬b+\!q@ | B ~BҖJx،D "-q@D@:J,ڦzAѦ  qXAZ ߒ p P-AЦm4侪0z٪n-qV>"ݴ'wURBUq*,bm"1#?_tb.CFGk*3l>t"dL2-xABr*ABADgtyѽJHt.{7nGংv c4UAtHOUs._* nq5!RGS?SGq "#A@*jW.qt5_s*qupU?6BJ6@W^Wr/qqb7ƮNLñ ppjufw` 3mljTU*qsX;4bcCr|fec6_kjtp5qH/rqj\]mMVeotlj~,/]KG]D/3hsDq8qa,ѕ*/6o o6x]joMϵPBGal۶0 T'߸_Q6=74_ogt#Xxq #iq7{wZ *11qX1kq 8ouGk5K,f1 ]y87q2CcGWgvb8tW6#$2BB1#J/2qw(Pt{Wz{[9G 0!'t!2t (N[6e: ?;l_:;W_;go;w;+?pxi%qr;ǻd˻;;;&]|{*§{?/>{ |;ك{<u k' ׽ɀ'~~ ?#{ (dz- A |zk~+z@@'t???@8`ABaC!FlPbE1:cG?q %8 KvbG L4G攱QgO e(eA2f'TOFKK*eDD_+6ئgD6Zo3;W\wAp#Eq)+jDxFc!C.qO`?$B(Š<̵2ʨ*zt}XL0A ܻy3)joŅ<8q澝?m\rБ[O{xۓ >y㱟} ׇwz{?So@K $@ %4B԰ +?H R␨QBA,131 S==j+k.I~MI'0)F\ +FJ : DTdJs <ړO@EkE3)pcѤ -Rb,-Qd\4pTSI}ZU\}uXemֹhC#J49jzXe5W]( C"Ġ+-6;]*TQ*fNУqDvPu:݊}7xޟ(,CxH4Ph {ȏC1م*":`>xEW*{dO5eWe^9]gW9Yg-Zxfc&e[Fޛq 躞7jH锯>9뒷fkdSqG.sFib{m ziۦ(.[ﱙ\ /O\o!\)1\|A]I/QO]Yo;[*xj^]y߁^=vꂒRY⡏^驯˖ PD/O_AcP`_q=c#+@. v8 s1A n;2`] QB`H5 qCýP=DPE4D%.MtE)NU&E-j1[F1e4#xF5m\cG9pxG;摏}H1Q$! H<"RcHGNR%hILnҋ')JQq)Q@xL']HCDASR!}KAS,&'yL>&St B (E"`t'>-~3df37t YTB%,rg=xO|sg?P` th0 ~G | t0j4'AN%!B-'QQ+bK]Ҙ3MM<C6nN^@B+LpRT4|GPdU*kjӰt1-KϺ.)F Uy:T ~hg"uPu@ ԥ5SZ vkg(K' Iduh1Źg92gY,iO+Ѫ6)m[خֵlmZ斵-n![ִ%-o] ָ%.l]nt5\b nu{\ꖸ j]ny\W%}+޵&*ao"BkN6ʍ/w^t]d`ZW{[ e<gqi+K챏 y,BQX6uR1ӊOujVeYd}YIA,?^W- Or ̈́-mK%rJ(4:H4'KGL4# Bt5_qZӛ)$1LzfINz׼eW"g:d+۫&6ld?:clkrۮv*mS侶}lgno[_o}aG7>PP H!q O 1qcn~Z$ꅼ6#'ydL~l@\V@ G\+^m.=;gkˁuqA7ё&Lӡuuo]ve7ўvmww{={w}'GX;4bMl&3jԠc\7H8D~򕿼AHw<)P~`o])H!>pǷ k?Bz׽)\ߧbcu#<3ƏS7t 0PlIz*Bfz' /PSSz  0 p*(1w4:pP N%0" 3N P,uR /f0 uT⧞B c0@pxnRA ,*>*J/yG0b " P wǡ\)3 SO *Q* ܐ uǴO Eq\ PP*PxNBB/gf<r/jq*pUzq`~uF  Ѯl!1y}]1*1*qi/&HN͠1Z ʖ0wQ/pY~*u\F@4* B ]ljȯ.l  "H4Rò#9,G"0, % Y*D'O$kҋT&$}Šrf$1ujbL*5uʋp+ l) (3lFIx G&*0B6)F@[-".Q.W1*u/S/O0.ru 3gqB1*S* 2 Su:Q02 oM4SG53Bvy/dQ lE/ )ޠr'3.Wb φ) 77S88:Yǚ0w912QC:Ó:sߔs r*SfdK6eEt`e5ZeWLgo6cepbAgO\A4Ώi_dOd{Ve5c{jH djS_6 luvg46f9tf nn1Rj6"r5ro' {#pM6ߖoGjR%|h4 mv@{gjsw8sWsAmזn?BfGmt"Qd*QpSQ 1qG5kMrrrk;shs6oVwgGX7q3xVtlwupnz#qQQ66DvqjwrZY XYu**XW'YuXޢ* W(Ql+?R))ir,ׁ+8{7~QUrWhQstouMb@w7{ծ7Wtٱ7|]g>'467wyZ[?Qﰘtz׋XgXf{s۸ޘ7t[E'?A֌%mnxHf#9'yu8׶}MyA:Y@1fVgE8tfP2L_Eugf6bGSF +:aB a_!` LoL&%_ZBڮcj:K> 0:c )X$0X`&[۲;d iKzB@E l` jg{  ~;{,b ] o:;KC;{ٻ;{黾;{<| <|!<%|)-1<5|9=Ag.f3Gj/Qo/Vq4^z3i5s1z/4-L(c NiG,$4蒐`)Sِ_/P%OHVȦn^I yuPCVd*蠄 L-"XS'BZ)Y .夙^jꩨ 13 䂍ZǬA*+3z+/ON'( ,I'FK_s\t轧]δ{ݾoq 1極,H+J+k,l' 7G,WKZ&;?+"Po`N&Lc+4|*党>QWWL@pFGv5q*TWMm4[]P/4sMV} 5`-6N}>NKGR >ZU +؍I]=Q5:тwNV}sb:Csήouϡ[y^{>MHWƗg_ \`[R[kU>]ji@?>Y普 HL:྾ptxOy xԳ-˃:`PcX;J z$0$ 9T T k#4| Zz !r U=%NJڒGz>񓞨ث 8 `tasvOiNRDcFܵqOvʒGtG/ac:+gtEAocTpkQ?&IJZR 8Pʊl " *RzR I,F},+Zu^lhau>rLb (UL`i/`fIM[Z3_ܳ (s]B(F]c`$ bIMY^̧>~ @JЂMBІ@7׫c>!F{QsaPFfQ+sJvH@9Ŵrvgp@XK MΦ)O"4y2}JUrқ*MTq*RQR(h.s|3v;j\5VS_麚,i lě2y]kףRI r+b*,[ϑ I]X4dm|IMnbC~8`};EpK}@۷xr:ЍtKZͮvG09 7piy/^Ayoz"ߡ 8^ 蕁 K`ݘc,1*H#`" ?!3+, ;$69ar.ms 8Z #(3񍁒c48F)2{"f(x{67zjzts5_D@c? Iyо"/k_{+ayJgⓄ莄y~T+LuKwQ̈́bL}i8-`+yM&DN-N)N,Nٙ9Yy!uUmpqb!qٚG{uwzGv.wzϹyuwI6|'IiIxܩC}ŕ|ٞYuGwpjɞ9z﹟Wyfx : Atg]'ǟr Ü0MF7#Zf%J!Mա2:4Z6z8:<ڣ>@*$6!u/p`[HNMœJ5'8 (k]*ݙʳ=1U2k9+];>|L۳aVDAdua/BGZ\۵^`b;d[f{hjl۶npr;t[v{xz|۷~;[{۸;[{۹;[{ۺ;[{ۻ;[{țʻۼ*vs'4Jg26 [{蛾껾k۾;{kۿۿB 0 lƼKpKPO0˺˾þ  0K Ȭ̴ |˹,Ļ<\לLlȼʯ,δl˸L, LΪ> ˲| Ц S`ā ;LE|мCP[À4Pq ƧL<VDLXS(, Ѡ ];T z!T TBQՁe0 =l ;ռ@;Pp}0>;Lu}p;cgMjĀ Є]ؼ0Rb؍ÏkÓ]پpم=T֞f֑n זȜELQLہmٻB-ĥm|Ӽ|-P pӸ\_=>VVׁ]lܘ +WZW ī-S0'ð L ZCC<~ć nN ^|ЙШSFzy^=b∮T.XY_@lGL9=s\Sp<^=ڜ}> ԅAAp>^N~e `NA/%v>.ݾ o4$_(46M)_@BD3PN_vLl@=, `8O_=e?gj?뾐lU~-\ a?v/'s ^Oq/ oK<k ߎjNX}n[uN' ܖ.ž;7>ƎiøöTӟ\G?荾M?SVS~K@ 4xB >?Q$hp0&H.CRJB]W=g?`ғ)Y|,+I?:& ] BL92tӨe3E (|J(/j V,Y}%mA=jw)ޯa&@RТ./d~gy;.mz jY+A@[n#o[FroR hW"VtP8D4G RJwL7]SPuT9K55KTSePCUBJW@ZkmV\uzU2`XUWQYuhߜjl[ݖnRvf_- ZtRw]Ix-u}u}w 8`&`FXfanxa'%8c3xc?dG&cOdWXe_xٕvff ygg' zh6Zgyi_vi54"ї.ߨ>kG{6l7V{mm){no.wn›>f7xqK~|e#xO'kdCM?uG']?RU}޽b)?gUo/>^z뱟{~W?'zw~ǟ~}ן'?p ȿ X@V[/?  )8B zB KB+4g2!lO;4 @ _E0VdXD.&pO"c&RqᓝJ&! zKĊ92c(=>,#ҘHࠍ}\ ȹ1nt$&IAqp&KkA Diin<%*Ur%&˱rhD `]*%Ђ/[JO;&Ӓ3\C4K)eMӄf5oyM8Ϥ&7)qMf9o2D;xҳY=Nf3T?NNҝ>zυzl>9 "kL?Jެ$%T`xIcH0HTs&Ѽ=`S!@PRA E)`x$z }#j$kɛ =&QTuEE`+"F. aT5V}VG;UFY X V"ibIlKX+Է,sPacC^eּͱqb16b<ɨE3搶`jk[zlu2ݢka\m;\Vmu\1]lx[\^֭ᾌV}4AO8jY~]v}*͡kJ׼7ٸ/.ta=k5j ա2**ۯad/Tzς꣐m,$߾ a) 'lETq_T5%qA=!2`%,˒,9âr({2BX' `JHP#f2hMe0>/]֘Gy|t7ӎ4CQCZWW$&Qբ~5cK{Wk҇vr;5xM1[wuiDozi(YOך6 :7t_ޅm%󮷺|#ضvn.v܇7pw8q_<_~^㛵93[C >$mX\6u-58i~s\vǭytytܒR׌|uHvh'~.W[׺lS_g{>asPz.~;w~G<ߏսw;ؿN@8Eb~vyIRYPmoJRVE:@JO4 )**dCEJу쿥xۮ[K~BlR/AT: ;t?r*}ӗw@h((aI@~Q } K1K.A8?#Z/2ۗ}Ix}ABI/+B0D AA~@m5(ȋ=3xz,ɲ2},̯}yB~+C 2t2C$T/dT)9,/3 A$BL7%9.ⷓ۸0r[Ė븆C8DK8I\FGdNLSD K+/C/˚~*D+Ec6|<>"E`C{SK$#SLEG\9b<Y>YF܈~ʼn#BB\+l;3 1X鰹I2vԗG2Gyz@x)xLlTpHs:;,7%/*z\x1#I;+Z?T~ɗԗa.;ÇHpt:Js~J- J> k|+Jh]aM5I.XRD(CܗD>)pKL_ Ȃ78EI4 ëD+ׂm*?5W6QHG~9—lȋ0 XeeE&Á\ȹVlEwIpYr/ESKlMU,h^:t*ZZVZiʎCuH+˲}QZ1Zlڬ}L7.HQ5yƵF:ې`Q QMUQ M9O|QQe˝\=MȒF_T[J[̵а [Ņ]m\! ]6|}E \/n\E͍ Bq̵AsCL?$Ȩٔdލ԰!^~Grmذ~FVc$5T˩IZ{0TI͢J1ן$B^O-`fe  `WeG,ȥBޱ1eX}O8IqWYʳ JEV5`F`4\>v>a{}LaZ2/#_KVuGT"uT)fOR-U U^:`ba.b/ʴ(STEL1N28b3~93%UU]c>N5c6T7K8CcIbE>)F̠==D:Nd:T4d?fXtdDRdG6EdBdQJ>eF^UJ O@r<.e_#`[*B-QPuPgff5fk^5hN\\o\ffp&^ȭu^qwxniQ{|eXNF>>M[VД5X95X&Y^eZLJtUfhe?Mfih^ab_ i=i@"fiahyi`$dj%lhThN&h qjzjyjxjw{lAS99kHk^k-ik~kkk8P0> &fN~'X q>2`l!>F˦l + ,)*腖0v .6&ޖ XB,Дn]Qnpn>Ÿ_y?@++^oncMݮ &kmwgmWp*.a` 0o p qfnoq QAB0%=p]jkkj i 'oX` )i,Hwhn^ r hBm^Ѓq"BBs)9;<>G;AttsEHEOJMtGBGtJtNtHtQGtK'Ut^0uTtL?Rtu[YuYuUgVg\wuPu`QZGtaOf_vZ^W y/a2 ?s^/v_'aG io2`hvhwsgv}Gu']7)x'_0^0ro_g@'!wG7'N1 8y( w%  o\/4m mpm'zxpp Ǎx.x oX>xa 6HqWz{{^pn7?)1#9%Qy֓sT){|̿W|'l/l|}o}Glܴ҇wӗ}æ}[NiF~&NVdNH.%gW~7leyZ /h>h2{:'I,h „ 2!Ĉ'JtH"ƌ/p]1+R!&Y%̃-cҬyq͜:gϟBkjeѣJ7.m1ӨAR8 \RaW< زfю-{`Zlվٻk;W޼} ׯath8?Jʔ-+9ŝ[,sҪEӭIv-;slڲa;z-d޶u'7‹]uj䓇N}w[g~Y;Qq~_۫V$AW&AU "H FՠMARRh 4GU$^ $(2` +ԢRX#R8:u}H$N]2UKDK2O(PQRW"qyM٥N_2IԚlgILјoT|'L~R)>int'Gy*IF:V5f:1vJ!6Sf銨jjꅰj:J+2kE꫏+.$k,Obl$زHm!@,EvE*ZK見.%mt@u!kzV{쁯,aV<+̨H#~+q$[+r,2-2z|ez{d^z|~ٗ% tL/-wNF_5au}m}g moSM6X0}wYlvwH=_bMm@8pl1gCm^%d+\ܦNꟷkA RBo:;P Nk|N̿λ?+s}G ~ثn>N[jrgwZ>@صZ]pu=%V 8Iar) ‘,ǿqsܠC8 =*RY+Oܥ0NsdAPRk ʯbD%Zхdtd'X.P *%T ,, DPX`C(*1 MYPE=$A!L "'Ta=@!SI]ʚZSҔ SAxՁ**d AEZT#V!$PP ]8$rI\0KzD Z^`0~! R~GM$ADAd5R)Ag'1 NnM@T־6AmmK ijŭW1w" 5Hʚ]0wm0f;!Teh%e "\$0za_~t5mj߻Wl2ޗ p2` 6Ȃ'Idx..<ʗn@*%aDY +D -A:<ގ>L73q$*8;9HVrc6R%(,!\#UG[8 5V.z]. d\]+(%J<:oz(YN퓑m F#9'+@C:;S{gxe12 *Ғ4#K85-ڎ4)}a3aJY=Ô{F3PA^M-6Oҫ\O- 2F9ʫЍXKƳuKnG)lx}wt1ދ \/W(Y _^\lfkJ!)$K\`5gK@u6µ)^,gꓧ< 7Hr!dR &IJH{]>Dry9օrHD8ER2'8}oڲu3`wbSpwi5{AV/A^{Ak6'ϿׇBR%Nxlٯtԣk/ш*|9?hEzO/Sփ>jP3=BMCG~顏zޅ?8OP_CJM_}?!8qX|}g_|*,edRfԑD_ɞ!:SʓLHmڣUa%eR ZI ` u `aa > Ơ rZƵV`&%_2 }-JJ1Wmv_FN9M`5m5b6a!"n! #$ #V<5r"4^ya~)X(a!,N+ғ'b."&S=u*ZHM i c"$& S%@D0lȩa +,!,bDkli%Atw7|#}1#%$߄ZZ.0SFNJ!R N$n/ҥF^$jNAAA[quc0\NfE1Q9&R$Z\-]o5C$ Q!-893ၖ60.ecc0Kfd'j!gAfrަf*rs.t[Dbjc9iw-nvy~buRuʜa"tpʧt.'/tH2zbg{ftZs)JHDE(g3{Rh &&#dzxBLVz3hG(ڍˠbKnQV% š̐!S:đhZ*iNhL&> Ni<Q&O Sn%ZReNer*n\!ef(}"heni]]*~fb:i(*z(aa}zj~"joo)l'9&jNgΧ-gZ*jjj@j"vg-j}ꪈJ3$.^>+FN+V^k:+n+vr~+bk++ƫN+Ϋkޫk+Vk.k+J*kl>.l6,Vl3Hl3P>,j",~lv,Ț,Flnl , 6k3p~@(+ ր ܬV7tC50+4p6@ Ԏ $kN:,,,&+ |x>+*NC7-7@ó^Vm3Z6`5(3\C^ò*+4l3r@٢m&­2+Rm&kn64m&~-R٦-bn۾m7b݊Ԓ.. .6*/5<n& -nfn3lnv~n޲o.ʦ`o&Ѫ.Bk mb&6o6/J0ր Hr@j.҂0Zk-"+ @6C o3@ lp@ $k to3PCR56T-颀6 j kok/p3xq"6qNqBqZ1NN4$55qα_CC3np++7qC/?k߱%kn^ ,!{q"/q?4D1%Ok'ӱ1'q.2 l)(w17ò,{$-r3hr42&4l)21p7##+?حp#*p6q57 mJC4Vp7+0+q0*0K1WvK3@ $Ep0І3j'6PC,S+[- q'kG7 |tH?!ItO5:orP'4~r 444OCrOG4P uRoQsRw 1G׊RZ5VuD/QWsuW'o+Y-H5J5Kk5Qg*v<,_`gT4vk"ݪp;++L_qd[c5tnQ]4tUBgK 3JECbF76tGs@:N_56 ʴH $(@U6DJ6 75X3kR/67..r?vwZ6xw7z\7jw6(3@Ԁww}3w?+g/x[qy^z;7C*u/x+17Ä+kx>8lik3xvw ,`ons6s8>55.lre[{p_9w3Owvpgup/+ +9m}O1o/9x90wDsD:GL92<緘2}øD4dPPo|;5Tv'[9J x8=l ;xqk/:6zto{:{#R6dzgz{7t_;:3;R+;s2d8: z'm;;/:Cys[mk1{3(7{C<#|3+z+.6GsP@Fq%(")*$dǵa=c-]K \(R7[4tBF-R׵_о{ V<\Y!8c`*^Dպu>1(ig.6K{<zFyF1bO`F*k&JΥ*ŖI<=0= O /& DI" I!%mjE.HFЫD/B|B 5Á!YdɁ(ˌJ!@K.SpA0)hL!L\36R8kLK1;h@IJa@5mXim R\I7&KM@8BϾSMm*JXMZU\o3Ֆ$n݈s=uۍ7w^ҽWywW`Xk fjby-݌5>dLVF`p_8f{]m 眳+yv#heڥ 7iZ뭹[.N[n[[\ /õVAqozQV8\9ApI|rһCҷ+]ra?sS|vc]y߁sYo]_>׃o~jWav穯^{_N~wщ~zo{tw}@.ڡ? AZ#+zn?VЃ!wDhBKBΐ5aw;;]n8:tv_D%.\_xNчM[&BԃyVF~|ۡ:1mtXőuG=}HA4!HE.t#!IINg\vz'AJQ69f D+aKY +N'/iY:^„DqLe.sc3LhN3Ҥ&5y?)ہv mrw49ksDD 1ySXD|' τ]?Ј2t9yX9ˉNIv^c-uK։t ST%P-#=5QT.MuSUNUUUnn׼WV$fYњVku[˶ƕu\W6s}WغUM+a XnͫUc Yɂձl'+{fVlgYVliMkWYPA=aH`6Emmz[Uo%[UukqV&-e')=u \unw ^~Wf-onK\rӥmzU ʗ䥯\Ww/} ^ ߷ N JXaXD9K̅ 4#NMKOt'f+buqY|X51mU8:vh;9dY@R')Z;y\re(W9-Lf4Zq %YV'I5{|f=p3 g*'zth6Oя|iC;VfaZ>G zլ殫jaxDe'jj:# ` ;6)**[dul'w۳63=p7}ݪ쮭S o{=s pj߶]{Weكxp-^n[lNJ< Ƶk|!gy]sd0?[~sCU(Iؠmwn/]߃\Q@2 <34(AzH*@H@ fL~~o網瀁) M'|މ=C ԞLH?_1~ct|A^-t!X3pN4Ij])gtmp{/pnPh/  ۬> ϻo  A2nD4-qS֒qNQR/4QSO 1 Ou?oBOIHp)wa .ѶpiClѫ/򮓬 .?0ؑdӣܱh/#/8透 }/ z.U!l "Q} " !Q" #yM#"Q$$EM '#$Y%YM$ &1L%?%&QM&K'M' F %Y""կ-(()R))칂 .+K'3'I (3,r>3?3$=γ&Sڳ!4 , t'Կk>20 . sACrB}BB-T05A9.g.A-Bk@SDELFMEnEKC'i4®)Gc/;`fꬮ܎aG'K+K/,#갰 ;J0`@ %!=d:+;T@*O/O/:4!4\*`4&mQ/ /QqiQQ]o`N; `Bo-QAP'QwTG1tCuHW ڰ QDOZOKS`V;ARU;NVS4/С2WqI ;!5#Z1S G2Fj_9@@[ Jo %]O qZ;IuUaT1Oc ^\kI_q]O5PV9c 4sewp1fSe)5[6_qSf7Qeq8e=sf_4shu|9ciU,/֕`idaaa;!N(iifh :V  n:1#u&u.uJY=u^}oU[ \ ,@>E@qW+QO؄SWX9-xQXgL)5M ׎ wIn6I33Vg YߖViwV6fVhug5 y-YYݶff7VfiVS?Y[9_y :[8{1GFx|^H 9qc qW/Fr8mq؅)tYřט͙|k9#xYIڗ Z;Rk֠ z)-1:5z+Z9A:E=zMQK:YYz]:eZgmausz}{ZFz:z:z zZ:gk"ǡ`<#:Ǻoᚧ庮cI0 <)ڰڪ;: S XF% zk`fO۱[۵_c[g[k۶Zw.: B |{AZo[*{ MLa`ݺm`TU7;{qھm[Z "):!\z <{| |mZfg4۱V ;ݛ]zYS\!M'/aÃO\ܱ 7: z}|ȁ\ A| l`ŏ!ȁĜ̉!ǜ<|ͼѼ<\=%/]'9:M} P=-}A]K}O|gc}kݱW =Y]Uփu֍Ks\v[p¥I]S׹}˿|l]ωۏ]əܡ]A`!ȇaսc{ fgY;Q{3| 6 D-| !|z\ػ~osޤE{-3ޡA=|GڣI ǽ-:` @~[ 7ʷzIE:gc[_[ۣov@E#?>KzJ?)Ѯ!nY8|I 37?;?CߩGKߢZOS[c_g_ks_[M>o?{߭?k;g&ӛ+XySN3ߌڝz_i-XY <0… &0ĉ+h1ƍ1r⅑$KhÆ@uEǐ4kv3™:{3hHBj$j4DJ2d4ARJӗUȜ̪dw=k*Zk۪mk-\rkW'޼bzneKH+˷&CG]#G)+l"K9{@k qZhױYMPڳq6pڹm{pSwR8⽣7}o˫ghw'/|u-޽>~WqѦ `s z 6ۄ\.`v7V("=8u$g h)*JX"'=H$rAaYP5ГP.$Y?iS\^FG _u +td y&a%\ک%Xi%YR=* Ei1P*$9IY:RJV6Hz*ERjdiO*ѫ|v [Vo n+)hI{:;UbK%z{%YKU++ &{/髮S:{l0I)C$TQ Ʃ!RZ=0 q&@ E 7D͎Mܥ) 9+Ԧ<; rJ r2wGG HTA$"A15c ZqUJMw%ʹuw@HL&?I UT}P5dDF5hvAb$yݚM7 z袋)Fy tzH@CuAwBF6N폋6mf|y`/c0v'8$@6>BOkXgDߏ!~ԭu\&;vW>Y{iԿ&rl3GL7yɭ'#̒<4ϝ{+ GgKj_M[ (wΉPi#"=`j,D8 Ѹ! j2 % x"$B#sp4 ntlwE@m+h$ y5b*c_fEˑ6b;LN2c C2$ RnR޲$M\y)UG$ GiJYr o,O)[1EYLd2V >De.*u-^fR:`c7]<&NSTl:5ORahA3#ßAAWNtH(CЇRE> LAJ_9b2AUE ,XPɠI#X?tl)NJR̔zt~ / PT&yO)R#EN꼐d_Ѩ'F/*1ƵR[zUbkWvS2V3Vp ,`T-& YJl&T˳u%m@,~ ̦h6lm?O̥5YpK{[hnrUd(܄\w֥nfJW|JcvVm<W2}0jG2pC`;= w}{w ́eo}31h B("w4_wwu b |ĉ1HoWK$P2g$j Z>ʚ.vtQcbz,D RR!v/wB\&Ƒj"`A<6wY siC@RP3AT?UI3s cm d㺕@ )D\6f4!jԘLR[Ѐ3H2;zf Cׁ: t]* /sm@juRy׌YHkNpR6iΕ["u diKr60oQ⒉@ Qӡ@\\}CqεR B8nx5 ,Hg3X,F!]rEl8sE.'_$L/pr膴Y`jYsZU ẉ¥|YsoÜXgNcG_7+8}fGH1sp j*K7HGC@s;p|u^T;;ս$8ˆj[;7Z~K|kv'o+r DZ n_&ѓl`Gڿ/A|?YZ_ -/(;]?>Ċ_vGxyT:`vE— ԰}y!7FE `#&Ƿ7O6EX~j 8-Dp=W'"$X-Vwǁt肴gcpc5Ti flUw`WVxXTRRxSUU]w_a9mtcPubq?k3#9M-ِ'[;'=9e2Qُ٘:6j2MIVj> *jSnuWHyQlZb "wl 8ÙB<~~Ae٩y7ڃJ6A :"&&Ɖp(hYT:sjΪJmٛiJj:,)ҪʚJ: Yڬ*誝ʪqBJ ˡ*y&*I갞ڱ>92j4Jj.Zԗ5e6 z8s ڳsb.:ajgZ z )M;O;TQۦf|~nUYIڵz"ESkY;G\ kRP!z ?K~ʝw.+-˲'˨2@43˸ *۟jۭ|˳ʝ;[W:[{+K ՚Ʈ  k׋٫˽{+K 勾髾{ K[+˿ L싗;+E@؛л l\ <$|)*-v* *p@.a`@uV [c&P2"K 5S ; ܐ 0l&@, ׻ϋ :"[L_]L cGe|`І AlC \r,dlwzl2|~ kFat vk@Y;Іq + qb &0<D''+ ׀ ` ؀ k xj :lz ͔L[ɛ ;%[G{b\ `-,;9<3$_߼$`߼&P[Ұk @ QKkF_ =ˌ ͜, l} !@;Ћb 1=5=[=}?0-4E=&m x{$M8`_^{ ,6=in pLaH@IWԈ!M] HmXRk`&;:+\]ـ > %m7 7--KM""&ԉk۲!}|ٯ۽mڿ}+܏Vm ܭ]ŝKܽ]Dpy|k @W-׎o}l׸ݼ}ƃ=ٯ ]k:FmCMjϋ ~;]P]!0Σ~M Μ)+^₽HH Gm'n-,۰!a,&#G O%m^8~J8p+_)0$ p ; נ Kvt=1n23 U~,@޽"n>d n}>Q^_ H輭Gd(@AN 뻞.T>^~rߘ>?N.n߶y܆*1m슎 , Nn[lk>~V.>k $/қFnt] lDp~u/^ߴ}߀ܾ~cd~ />:i@i7$;lc7!$@B*bTwHaĉ %1sC0BTS|KӨ3 D&]sUV1">H^5_Dp@wDjݺuM8 =;b@qbw˪{D x{R: ϾΓ|m$nz=Ͼ>C= 4pOxK)B 2fn1&$"ĂF0fFk"Fnk\[Ci7B09ÞI`?t7N8m,(At4?Z7,N<ӣ9<=#t< EtсeQG}L3DSRLH7SO5)G%S!MuPSWUJIDiV;[3]wIUX1{%$Xcmc-4Y'iV[Ŕ-r5 Uw]vu]x㕗t^|^}yx``Xav`#fb+&b3X\Xc8dd=9e;Ye_dgf39gUVsP9睏v8i'nfF8j/:e7k7X͵!l쐹F{ߵ5Vmy߆^~nVwls\7O\oq!7X|}lr?r GsA\EtSMrn׻R;ќtGg{{'^Mߟ_^y飯~zg{>|~_Ng}?ǯ|W~O׿0d:>6TAo%A p,aWxjy(|ab}3a gxC9a64(D .QכjwqsZ";mb\fFi`c \ljfǪQjz|/r z2G",ra$%=fɸa4';vj4&A.Q-<[%NIW2*%bIRҭvJsf„Z/gLb o4/Ig"d56[.Ӎͬ5cMi,9JmnS8NvvLg9Ksm'>.TgF!|/ OSfNGrQ@¹ +45Ғ1$9f Dnj(Em~$00N U0$D`&1ABy\i:kG.7iNϮBH(HvU麪Arzk\JUt_8$h:Q4) K֕ˢ(+\ jRUN!tkf7S4+"D F|NT"Qp"k;}"msԾV;Lp [6e!s\׸-C&a]vܽN<6:+uٸ~=xKVʽoxN{ r^ -X [:W[UʕͰA DV3NX{z.@l ǝ?Gv LV 6XŞ3sNphF?Z KK:қ4&)}7=OτClZ=qCԨ>%cY:բu}k`[&v}ld'[fvlhG[Ӧv}mlg[vmp[&w}nt[fwnx[w}o|[wo\'x ~p'\ gxpG\x-~qg\x=q\#'yM~r\+gy]r\3ym~s\;y}s]C'zэ~t']KgzӝtG]Szխ~ug][z׽u]c'{~v]kg{v]s{~w]{{w^'|m.t@2dxG^GјnG=.jPhMzC3h޸zь6P^uf7>4Phn{'_p,6Hڏ}kO' OS*{_z2Gw86„d L?{!,(((((((((((('%#"#"       """""""""### $ % & ' ( * (%!!$'()*-35666666655 6$9'=(@(E(E(E'E'E(E(F(F*H1M4N5N5M1M,L)L*J)H)F&)E*+G,1J-5M.5H04@1060/1-,.++'*+"*-).169>>EAH"DK#GN$JR#NV%PZ'O`(Ng)H-?/2121212222222121Q'ew!gr hr gr gq hs*ju1o{7{>>DJN~SuVoYk\f`ai\k|YioUebU`[V[ZYZY[XWXWSSSQQRPNLPKJOIONFXLE\HE`DCcABc<< H*\ȰÇ#JHŋ3jȱGϞ}Iɓ(S\ɲ˗0cʜI͛.E& პC ѣH*]ʴӧP U~~TSE e5ٳhӪ]˶[<-+@>>7PR&8È+^̸Hn7B\v Ebl9ӨS^ͺuQȦK`Yc;Z`慼;?juhR캹УKosv`eYyB=.8݄_qOOOf_|\Yah;V{֦Cvm`A%`~fv)(__?UU) HgU H^y Xf\N4eEy6\E992"CUI7'uix执]-aB"MVG 'y]F*⣌:4VyݠٙI驨cC]Ym7'뮼&gB ]oYl&+uױmWVkzdf;v+覫Ю+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z0*GHn( 50-L 1C~8!@ H"FLD%"P)ZV"E*v` H/hF*֐D\cD9B#C=# (Aޱ#"E2x= ܐp%-G2ґ #(CiFRr"*SFVq",cFIo 򆳤%)L'Sl#1Gfqc4iҖYMk갛a8K9N.s g:Nk&%sU4g>UO,3tf@9dMeyS"%*UjTB\XT:PUz՞fl]Wֱڕh=ZVq jM*زְJ$[נr=X#TucX6ukazX&6,aں^kegSjֳ,`?k5pa=<.>E6׹nt ń Ӻn,ɅJ@V!WeezJ궷5e|I9Pַ#q7iUx̯1\GE#I'̈́7a İ!5|J KGqEG{&8UC+Yb2n$c<6^.!tD2O\X$2)* fr gxAr*|C1ͨXcG|H|d q>Hx + dt/6yL; {3A"Mhtq n!Jw :Ak 2  w@8Ї; R  1L@ VtRf][!ᵯl &69AFډv"@AhQqc- |8`[d 6 `c3eH߼~ 8D ]H RkrMo܉X42b(Ǘrris\ڼs1H=qCWrC!7|u|7th))Uq >v=Zt|Hd b]z+a6A肸 b֓׷勞KؚozЇ7xȳDwpNUOܳ7|kc-|;!}4C_bŐR=J  'wèC wٛ~_p'7hivfy0Qx6~ZRfd&f "8$X&x /C Cbg2o4-svg8uxCxBr?ȃ+@BxPD <[2E\5X7ȅrC$XXQuR=u//xf2IwD2^o o@uh2yR}^QIhb؅Nw;Mx؉=sCs {sfn HV Bp hAi&h.grFgvgŸJ7/pSoކsnٗmyUz+$ymFznlŽmf|zAyݗs ht|j֎wlbu 'z7Ȧmh/s Vhsr~Hlא&Ts svtG4Y@n6)7rc&:t x/puŊw|h7v8zgwx ȸx+vvCYf~7x<vWw8TvBAyhv[Zv`)bifU0w@xqy+gHaz=A툙yy{WqV/T^<A9|8׊<}2Gԙ ю'|< "ǒIz|&~(Z4(GyKZIBO.^(HrMx+1Qfzh5p0^pXUDHC<.|VtiZhzէz z5: Cz0.`jGꤕJ\Z\(jȩ2 !d\ڪEjѦꉔz/: :-*Z꩷ǚ5 :>z٪j:ZZp꺮ڮʮ:zZگʯ;;[ ۰z [Kf k$#[(',;+۲(0K2; [68 [ B@B o<;۴N;Q P;[V{j*6J`Y[bۮ`状j;C@l;Bp}wyj}0Jj a>˱30*䊵z믘"+ZJ|BxкjjlJkk˯;뺰+犻3`30{t@z@z[˻ kܻ; ppk˾:Zy b@Jb@<̼+ +z3@:;u;z,jH[C^zov*+=K k2˯\˿ T lHm@MٕđZN<x\J =aڈh"3pK|QTV Ğݹ˝m=MݮU{ -KmحޗT 3Lmߓm ;)T/~0OWI` FġaרJj.X2R< D( :*^A*zƺ&ҦPA5>ZzD3Mֲ#]/-oʪ`([ 4*3U^:h.SII;u*zZw>&VmhGsjrv OڨjqR喚^-)n/e^@~J@~=Q<^/DIg6).AڡK2>./>^~r <ӦsK=1/,7#C0rt%N= IC O :NbSLXFcFOMd&obo%OWFe_su`B'/bd5e9a7O"MrEF*o+JGoaK!=LU&YoĦPwTX\oeo;O`Q%mQY\z5ZV%\}ZZZ/[uWUV~O[Zu?Y?o__[.ZxO[_/SvXȏ/|8=FONgZW]q/ n]/T0OEA@ DPB &rQD BQF9~ cH%;DRH-St3c"5mބy0L3{PBՉTiѥMuzjԡSęfCWyvlXd5mڒkنtc\jZ^tV׮௄e67qpTX䗔'[n9f;{ cҥMFZj֭][lڵmƝ[n޽}\pō?c'q͝?]t꥓/^zvݽO͡7slrў=@hfH~f@"Z/+7'<$@ y "C+i^6?dߋЦ +o?ipE_<;k ^z#D.Pż1 p/ #D1.qC)R*82M5d@?2,n 1icp;8뜳N,L#GPΛư&Фi )~ʹXcECiCZC+W`m ei6g֎iq6b)dWQkkR59x&;HiMqG 0+Tw]f޶]*]݁㵡b%IO+!`1a GH>W;p7}?\$.H ^X 18U:js?Zoi i괱hŞ[ pz1` İn[kW=IK9o7m&kZ:V[ɝWE*]^@/sde i)c5W"Kc>D?]m`/ݸvۯ>{:U~FUzt=o\-zÒ9w>@M!Kً>PxN z -BԿDTaCZZ7B8QM,<_5mB6A* Š6L5C0 ъW"w-fы_c8F2ьgDcոF6эoc8G:юwcG>яd Pā<d"HF64spTqGVҒĤ!98/e(EM De*Uq4Q+[9KZr(E.K R%.uZd1Lf6:8e"x KgVӚfl>@HOt<0[69NrӜDg:չNvӝg<9OzӞg>O~ӟlgICEl8")h>4(.ӓKhF5>D}Nrd*ń"Qt{!$ X'%3UN4jPʠbyL_m Yj2L`؅H*ΧWuP*LMm?kl58X.le@l`UTXk\m94Vݹ%Zz\;Xp{{=̀%MDXVֲY(Yh4A]E/;ZT9EmjUZֵֶmle;[ֶmiVQ' j-^|{\:aݖ[4&;M8^؅u[]鯔fqmuZyOJmq 7}~_LW/l&YYwtx+`:VR6`8.MqKx5oxA>rs'GyEr|-_ss5 y~9w=AЍZ JgzѝU_yusGzScO kQ z8_M`D Hc e|?<'xkgw~=AK/g0#g>_Q01A ۢ8F1?0^{Hy{Ioz3~G {=^W+{|u_;~?S}^۽=b?[Hs Äzx<ÿC<>==?>= ǫc?S;; ?7CZ=LAÄM@t?Ћd` ,T=K}pNXZ@˜8RIG|W>@@CFEtĄ_7bHBb`ZH[3`ߋŦ:$:;DND-L8R4E+ƂEYEWlXZhTIć~(FLNcd eL{Fi|EkTi b@+nF G?3GtQ 8fdGTVGZLZ z!:|Gp< DOHdŀQ7Q(>B|=^Pb\HE&ZF̻vrHc$Zɒ$#Qɐ9Q7D|0>9 dedT8RdWhFCCL$KˈMMƼ€MKLͳ\MKKפMM8߼KMS42,ElBtIcDODb|B;8Bs,8δ\K3< ZʒHʫ#:c]PL}BLc(Oc0b@^0Is <}>5QPc 8EuMF{CX;+-| !" #MQ%8&=K)UBIR,8-M /0E82M҂CS']ڃS3DUEu;89R,PXPPNN+C8PDGe@I[0L'mEsO^ͅTwXP@HOO0LOC;m|Me =+RLJTK7nE6NB4^R^R)}+n_cP-dcB.8E_D4d#EcB{*;ڃd$/dDPS^c4EF\eκHb!B"cZLPOP,CRa#e~Ie8;D!·"~fghfUIԸ6OyV{KJ;K_ .h#CNeA}<~~:Av%=eȿKJՄӄɒ^0cE+c|Il;ɳhghi6C4\iIk\lBvoTkˁ~k.J0P>8F#̽vB>m;{ u}gW^O9:8m8:ܮmmm߮U8&n8Vc.8`]nnn.n^no~oooNo>ooo.p>pNp^ppF O q '7/Ggqwqq!q!'#p#G&'q&w(p(*p*,_,*./&1?27sOu _O647s4s;r=wr?~r/wg/g~GRD, „)l!Ĉ'RŌ7rԈ#Ȑ"-~iɒ(W$%̈*c|9&Γ7s$Y'A;{8(RG2m)O2JիDjɵkKbɵuD-ۘpڤKuݕxt_kUF-b-TX%L垖3Wy2E MZak5icmzuwJSfpum/y'z>P k;/z벳^Ծ.|3<7?߈9+'hg#h9ŏ3?뻩ت? P.Uv]%.V[TcI + 1Ojn3aQHo-{!`5>MJD< X+Gᯈ8% FsbhmQV"-r^"(1f<#Ө5n|#(9ұv#=dd8A (@k|$$#)IRGv!,8!HP#ѠAJyJvMp>>AJSR (3V~`d+e2|f")q8z|'7D- {(Ї@o4zYIFӜ'@*ЁbQ$;3DR %4tO3E JM̳E'ZQsE?5ҕ.-A9@cCOHKm}hAvS_J#L˱ԩRVopu>V=)~#a7&d"1L"록z|>K`)Rdҵ},dSv PhAr ^fԢid{Xq(h1TV.6}-lc;H,;aSh e>1 ܒ('9{O~^JkMm$L.xxLfAЁP(i{^8h'P}H 'E+>@3~0#, S03 s0C,&>1SX" 8Ӹ6>j>r>j1!F^r 5N&C>0)'2d*[2٠d @N fBa>Y9ӹv 8!N~3*F*І>4E3ю~4#-ISҖ43MsӞKԦޣI 郴F"bsI $?b^0#(!S3b9Qݑz׽; b >6PVq\U터MQ| !!9$0IPB uApn\ŲˡAo} 8A.o|/ȝ8A pЄAB+h'Oa!.z7;|8ͳc :yŸ+OD:!U<G/[%$1{"p Pw>d˦:[8ə9ӭvp'@]?@Γ!؇M3: 7B@I~D]O\1j(CM$%B?A"\/>Z &6?Q_Y%!^!!ơ!֡!!fBAZ K9a"^P(bI"SlTz !"'Իa%Zj|b)2⊙"*&%R)UBG'֢',*","",6"+-@*b/1X4#b,"%!ī #7SALΌ}ҪX73)cTc4>341Σ5Rz#?2*"*+B*J)U#ABJҫ d?F!E#6@"6%JFZ$3fD$5HI$JJ$KKQĤLΤL&M$NN$OOdN$P%QQPR.%SeR6%TF%T>TVU:UfV$UnWfeW~XFeXY:dY%[Z%\[%]\%^v]%]%_¥_%[`%aUn5L5\5$baNVVeef%fnfUvgeLvOA=&NCLf4fhleL?`G?\)T^fkʤ,3D0Xt(BL&RtB'm&`Zub`jvrazwd?AL?%MlB/Ĥ{g|&h[Ƥ?BL>fNV%%TB{g}*/t(C}RfZtF'cV'~z}^Zj(rezVf(Lg%(O)'N&..$CL(ʨ}hbve$vAd0<`6vAXZĤM➘LNiJ%^jRf&%id%2(ڤ)Oi'ҤdidvA+P'(vAL77Bj4DN*VLj@d~j"*&RjF*jnnj֪*j+&*+kL6"Fk. k>^r+k&vz붲k+jR(^g?h)i)".*wk+ҫŮkf$fG=LA?lLܤRl.. cĤ(TViJhώςhzhOL\h}r)Sڤ*QN6v0$2gZʄvtn)rjh^gۂg%.,6*ӖQ ?*N$tjl'$i5Th8&dJ&mjzrgjPi=gO{fLCbgRR^$b? M3.DC=esZnz"edmdnzorodznf"oZ*oerZi&m/sop 00b20e.Ow$_pYpM:.*b(N")c ;SGVL8#$@z$DzX+\0>G p0$c@dmk;0) 50Wq11H#5qppB0AbdCzq=Z1j?n$6b - 9އ9Κ 89j4q1#GOr+Mc$/$k1ʠB# 3$'˱)S2*;(.#+*d2氉A#11[I\]? %5kYCK%&ٜIP$^5 ٪G?4C,w8SDat5%1cv}?!^P_uM/>DWtA 5#So5r @` `UYxZ0W5s3E&:c^õ_`6aa6bА068 !P ] av I6An Ӵ±-f7i?vjG6d d<(igmovk n dyvyHppwG0Plvu o Ncwvyd7܍q<$ N7 O{|O}7|7xw7;{~w7+87Cw4OwȂwG8Nx8~ |gx _x8cWs8xN{/8xǎN'7pkcb8y(wx#wwrcr78D9늕QoSǰh̘J?yojvnНwƛPP3ǡ;GCǢKebnyq7:uXu<=Zmv$qKKzKz5pB+4\`:=0a <>38A!yگCo<J5{:?{۴{] e:$C^{[O#;){; XXQs DC5T@5zcs? A? UES/{cF[!D T#G!ҩݏ#щE>׻@\tᷘTo DWC_Ut̥tTô?2H!):~ĉ~KU{~!^]t>!پ  3>ט[WJ?>~7SJ۽]FG*3<$m~ WO^O^%DFuC=A(~B`/5B @,X`|&YcP~G&h03$cQ؂A9dɑ- $h ~xqAƍ5;ԹgO?JR#&1l;ʴQLfkW^;#ڃhȿ֬jɰ_X\zDq-,1#hFXH' 6 2ʼn?y2揗3tjwCCeөǺn#B٥m>uciډHӾ]zu2gk§JX✇#1ǿ@4Kbd$f:v:K$?o@Go?nBB28AMM-\Q $cD '+1:Lr$K&i/El"EAarTRDYgLRzgG3^y旉raD+|'A|ͧ䜈N;>MArGPqS"8i;sHR.-MNPORiMPKTSj2O|a {-sQSYZBv[ܺ[ռ\k=WumwWy{W}X .NXn!X)1X)zXG1QNbDr/Uֱk"扚yf%"Z&F/٠OcZꩩ@#i楋V믭&inٞk8f;mN&F /p 7\^_ >I)Qٯ[]J'Qb%`-qK]/La4195, lk&Ih&ZY"4;1ko{&PG+nSz4yãycg;Nsth._IU^u3uor5#jvjyWu=u/:urз˦<~tjaٶ*^}~ztY8_,Ɨɘ5qc=u!CNDd%/Mv򓡬#GUҏ%!mnw]páQZz䶔}kgfwxy*vmp$S]Hw#C ve0=1dM\kHr/q^  81Ad0 n&-dLgt_XZɺ浯k u,UB?TAk|P]ջPK r/݋w >en{>&|}xwx?x3y'eUc75#]A>{Ma{oiO^mgr_`DȚ'R~}t `ϓ}o_{|?,o%!yK|ݰ%˝G[?jXMɒazOȒ"/./)nȈp *A 6r .pHpOL naV0bcRvZPyPKڄʜO<4Ɋ w!\`  >zP/Ў , wOՆ*Z M,mP/ߖ!԰ l"QP z#1p>'qN׆LQwA-g Po3P]/A1V! o֠ȴAH. @60cϳb lL 'V 0t/N뺠 # ("ۀ2r".#%#+#1r$Cr# !qF A2DR#9$i$5r&k$ %[Cd'{$or'R'I'Qr2n%%%&,)&L*)r qR2&co,R2*)R%q1A ɞ<嚐ȶqq P!ò.r"E.' )M5424O$26QR<+/j3'Y6a3'g' 2 r$?-!: n̯a: ;ZaՖNVA5+ڀPb f>Fi^G}TȔa @:0CTwG9ITJeְ-HkKkPKCI LgQLiLm42ɶIK&jNQov`~ r! ȦQA`A+B+3 S8/lCKUO/DLH,G!QOM;y9t5 ("֜ !"Z Ў4+DYN$U %5Y\\!-[/][]UU5YY),صU\'v``i֚ua,Q 3WnnjFvl掁2-P5:1vjvZY͂uXb]cQrˎ*Dilijɐjӌɮkki6kVivlmLmɌmlvn-nn'oopVpŖppqLqqlqMr'rr= sGUs)s s;qltItMtQy>iauU,ue*,tˤbu`kZawgwQINz|l x0L򫹨x_'y7uU)"]7kFhGz{zzʵPk(H}Ѩ77iƗ|헄j˺t~Ƀ7'tƄ"8,Ɂ)-185x9=A8ExIMbҥVw"Aef5$"CFH~8|=Farw8e"HHA>,($8(<Ȋ3h6w =͆V$bh^Cކ/ņw{%帶z8yz7bP&Cʔf(Ǵз}K ط*y}z(H /&RY#fDei6)9>D:ry#r`9=xg6A>$W6i#9'CfdaSfrEUx*f y \9yٹݙyx鹞˞9Yy9y :㘡:Y)zˢ+:593AEz#MQ:zY]'`W r)W=A:}:gCڨ+zzZ7'jt8A 8awڡ:wZ'Zaڬ:zzmzZz]ƮZٯZ7"IڱZ"0¯)7{;; &1YO[>"W[ RU{"X][J۱Rm;m۷%{qd&h)9r;o[ [; '7 Pgۺ[[{q39ٹ{՛|ᛵ䛺ݻ{;!{ٛ"\;ܾE<ۛM{ AGY/䚱ZɚǿȹZAYcY|ܨ<ܧ|}BLhȈ!k [Ӝ΁H]<\<&:x0#='ݤ+3EAI5Տac}QmqtS8סi rX$xS$"w9d"pX-}8pAbYIj5HQ݃8$>vT$}j؏>&M4D=~!Fe]~Q@"I&XA=J%|$&Fᵸ~k~A;(C=&bTG^Gb(A"(AfP\2 KŒGÐM#2x~)7JeIB1 P]DY'u$:!$":!Co^'z^eh"]>gdd'X2zX"AM;h6V %L>?^$Vh88^~'rPސYA$XƜxʂ!)蔥&TY N IYI"&aVVY ~}3b"|.*^ @B}r@?0N^r ?-P9.{'BD &a-l1ĉ+Z1ƍ;z2dC> N蟻J&%9 *d藲̈y#tÆӉ*M;5֭R$5Һ} 7DP&Ţ"xҹ >#Is }9M9/GC&ʡlW")4'(zbU;9MOM.]eG!,p Mi%ފ*mٳ_5ճ?r7@z߽^~AR,2RgP>+3y+gdD쳏*>1H"P߅7DDgeHèWZ6ވc:cZj>IdFdJ.dN> eRNIeV^eZne^~ fbIfffjfn grIgiUQN"U~ar19tNJ)x^J)rZvl%jO.Q"Ag%Ӌk}yi+"62*dY_5O l;,-a|5j{dB4@ԓBgVR<0Nnn+WuR$IsM81=UgVu¨^Ip2366JԌs;ی4}<sBttI#[x>q];F{^j{fZ vp=|,dG|bge'qR%+sv-w~u6j7$1@YzGWo>Œ-x%y馟:z뮿{N{ߎ{{|$KW@+_%0 xE &ץO"pL!h7TLza}A 2 eH!A!XAkD'5}gp{K/_DNp,l _p4 op< qD,$*qLl(JqT,jq\0qd,ψ4qll8qtPrDL>JQr-;|@ /)Xs0{!,121212121212121111111111111111111111111111.1+0(/&'''''''''''((''#   $ # # # ###""""""""""""""!    !#%$"!!# !#$""'!!*-/011345457;#E(K,J1J0I,H(F'F'F'F'F(F(E(E'C")>"+:'/4,0//2(03%01)3-35/94/A12F00H.3L,5O*8Q$[)=\7e/Ji.Rn+Vq*Yt*`v.l/l2k4k5h=\|GRpQPfZR_[S\YTYVQURPRRPOLNFJK6FL-KR*T^.[f1cn*hr#hr#ku's~/}5;=>DF~~P|qY}j\d[b^ab_i`ohtrrwqzqkc\VOMFCECECA@@@??@@Gakx>66;@DGJMOPQRXӘĚƞŤH*\ȰÇ#JHŋ3jȱG(P|Iɓ(S\ɲ˗0cʜI͛.EɐbT@Vʵׯ`ÊKV%σ[N<ǶBj"Kۻx˷_X @JݬhJL˘Wv<,Hϛ ">,sīَmgM۸[Ψ!&zƑP\jBê2o]7kݩFsO EFG}{RϙN/=sR m[Uh͉ZwǕ 6m&AFBe\pUqǡ[Ɓ^v(Ur(4xt g$wETt'_\c wtHQhi,ڨ\vH@fyosHj-W%|96dݎ=lEi_|i`FeDwlE&袌  Y_Vj饘f*|*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/k. so)Q pg (${*0Nz 2xA` GH(L WذTl!,THƅ04?v ^.L xE*Rq VVD l%PT"! "FjЌAcAH6&ł܀94HhD7\nPJAčgDM|$B'B*7AA FT9?EL]yZіHI I^nFTHR iJRk$oIgNs&fA h^AIXN >X7a,di2Rt+@ṛ {ythC3PJ"4#Jl6 ^8)l( "TD2PR/r8gJqⳚT A*RL`$@PzP"`ҢPTДhZQ  >hҏqL{Zo*@}&l5V9b}^c:8`GQvūSU?hFk䰃-,h+VmbU[XX)x + ldT4 hfKN/=..ۘSPq \ᲒqA ҁXFqq#_{ʭwư5g%ccMWݗ1wf>׬ dd3N"r],gs@ v5-;zь H[Ҙδ7ND CQԨNWVե~gMZ:ֶεwj\MbNf#Ύ6-j!-kms۫6mjqJ07ǭu6]y j+  m+v׍6(GU: P KAxAUD8Gۨy PUl2VHUoSp(ΠFArN5ԣ^SWճj\" B*Tma{p괷:ؕ vnWA͕U!KuG+A"o+]L? ho?/zXOMԊ^ ?~Ա}w5C|ww+hn^?ԗI/_vMU'n~m/|ݯ;K=w|$kV@E'j7X0_ygS}4%jW`ZuXpX Q&j+8*0(2(1ȂM6H,P'؃5-X}Cx33HL؂RxES.HON^QKH\H[BFcȆ;b؅jhqHe؄vmr37E$(x!x aqd|JQ)s%'a|VsVjbpx)JYg pu 7ynFjyuwIuyu{t}9HumI kfuml)izyjiYw癣( &Hۈp)}ٙ)I ))6*`A?P:&Ԝ8P?: gٹ:B߉:5U:]?9;ttٟZz9 ڠ:zY : *$z ,&ڢ0/4jC5z98 :,ڣ>z@#:Dj^*3DltDjPR:ZVYX.5ڥ`JcJug֝V`eڢbڦp*srZ9fEʧ z~ ii^UT ʦz**I:CVڠʨꧫʧWXSڪ: %@PhU ZZZ$xV3X)hXGh0*FjJvʬ**jڮڬ᪮zjzگjjA:Uتʰະ몰k۰۱ }2 Z#$,;Z+{Z- J+@J:k;k5?+˱ALv MʝjJxuʵsp m*eJcjP v K)Ihqsukw;yXK}[+5@`L?Y˸G*Q qjJA1 Qyʹ;*\ k+ʺ뺕c DiOʻ{P{ț˼4 ۼ+E{k͋nzû[۫þK^?ў8Aiuƛ L8cs ,GDKIOQRDEV{4cGEbr S EuWxZPN<RGTlH &Mb[<+!Du5PJ[hBpt)@8` #4EIo,JbTJ$ĪdB xe,VfwȉPg IKo Tʹ*OUd+%nNhM$]QʺlP-NyD˻%P[@]Ce̐ɠQ̂ L\|P*\D̻zcQS$ t\F,^0L_*B3D=LW0CäQNlT Qxm]LUu%VDe ZYuYm1W=-1=V=CEYidY%F 11c%ҐX$^&~(*@MC-eJ--f0KDO->snF׬g\ά EĞ.6Qu~ۓr~KNMS}^HrϥЙTK֫N>IZFW~ZӲnޞn ^P C}SV+U,1NZ>_>b~\%`N>eXMmEצ}y}]]U`giྮFni0OK/UŞCW`Q ` a!tH3O]nۙ)dOc5=FU?IWYM[FnM^B֭GLaO]MBu_s3|~/*Ý6'AjskS*{{W4*?S^Wd~i?55f ?) ?_؟ڿ__П?/oӯ!If DPB >1Ă#^ĘQ#ĉ+nRÎ?DrcɆ'UɐeL59R4hSʙ;}ShEE@0-8QkU.[٢ʬBoyKǐEK[Ȉ $Ur4\ ˞ܹs>)VPpZi۶L'u f)m.UU-W0af T1亓+ysgC>ВfAAvNㄑ ,B͍o=g&9~%;÷%ގ7| 3+4KMI{O*n--k +Di=PCLۆ:jjɥ:p+?Jn))'_(K{@QGlEZ:3q8aK!b e2IO:R.2́) gDH4/N<„8L0(2TJT n:N/- QB%1сM]RI)ӳO=TL;5ԁ~U PgV-PhQtW vSb-6Jefz\МuMF=UN:\q^Iw\% ]sQ2dZFVxE ^lHOII$?[0!ևhgYs t6iB&I$XYȠA}!"D"BCD*'F4M` m7a5 )DM77:8ɂ"cWAj]-QwH G3.H5d YCNT-!.$pbBGcHbӻ 9/vc!+3%MDv v1dW_'YDSYI80}Ù'Oc;>LhFs΄4M2vdaUP! D.ܾHF- "xq:2A'2C/3S䣧(׿!# xsiɈxj4neب%f84z# TBh:껏&TdNm>Si{j͡J@%*RZͥrs$5"rzdbI=KjQ,լJ*ZUn!q /q#9_ShDT9JdN˔YêVl)@H \zϛ7_ϓًO=+xj/>>o3={~J>xG#շ>Os(e"~$o?`o?5~g?ͯO4DTd<@t @ @ $A,Dd\|A<A !4(#$TBd&'B),_Ay)0L1$C`faDoA[ dZ&rZ0. 12n^VaS> ob>yeH|N {V6K.bs6^Zteha_e胤c` ބyh ^. /0bYUw.oUoXF虦鞭5Z`g`P>`Ƹ_Vg8b'檐F>+f؄E_x&6FVfv뷆븖빦뺎A4NL4Vp6æY@fv~yЃǶ8nl4_юk` @&Vm^׆ؖ٦ڶ~Zg(d ~z`n5n6kb~`0 `pcd g') tHrveno')9n6gr@xV>bpb8 cx c@p o𳍇bpg@maHdhqd`@moq6 !G p5r#Oqq)G+7 bxqb]b'$W@s5G6Wa G 7w9q,s`7t E'r"o7?7Z?nss+NBO cda0(Sp84H/rWZb~L8&? _*,oCgvu%}X_ u'k u`8o? /uVs_XcWY#ooo}d_s)w)ZowAKxuw)q}wwq?&u)1}1Yxy[y#a;GnL7O qt/tzKzP^HhiZsO G pP05Xwjwq_ /\z2Wqq~Svrv.|q/{{x/{HZFjG_|h|vuvا}/p66w{{H}`8\?nc Hu7 s?gt"ηo 4pE~iuwK~f')apowwgs%_(h „ 2l!DΊ%h"ƌ7r#Ȑ"G,i$ʔ*Wl(H]˘'Vi&Μ:w'РB-j(ҤJ2m)ԨRRj*֬Zr+ذbǒ-k,ڴjײm-ܸrҭk.ރ+/.lX)14n1Ȓ!(Q0̚7sjYl4ԪWE4زgӮ}Xm7pGWn8sVv]|9ҧSn:ڷs;Ǔ/oח? x  2JZxjXaz`E8"""#vX#7Ȣ;أ"{ #:jm{0d!s1&[,fc޹v,LAw˪G0%[o2{1g,O~^4 _o@hۈ9K]{sYk}Hc^-\]fK6nMw[}yqwہ]xz[j8mGr9ˇ[w-9>yꥣ^.:띿ܫ:N^;+^{A A@l0 |~٧._kxY><ߙ;!HoŜ|8{݋fa@QHV/M0A%yR!oPq{+AE%S4C@, /;tJkT-_H);DM REQȮ=`FEo$JIjS >h&MVo4)IKjR*x+nXh'=Lb 1폆8ӉC&|4Pbg>S-<E"^8!zP) S.Mr%`*Y4rT)iYK]%Laei#RRsLiJ&2fk6#qf)&9k|'<)yҳL)0 " سxH!4@(B鸦 hB#*щFi0r/bc=)JS@T.})Lע@)NsSBN*ԡF=*Rԥ2N}*T*թ'4Veխr^*X V݈f=+ZJִnZ*׹5t+^׽v+`v-,bj&o=c#UJZe3[rvd? Z~h +"~ Smܶݭo uĭk (1= C-V%t f%qŒ5É0m/Z _7oXIu 1TE*eXUw@~9z5[5$Ob -vqqbyӌe kul 6QY{9FM,NUnkYefLŢLV%H,dчФ9*Ko r󑯬؈f% ii^ݘ^k }D:kjHѡtA&Iқ49hӛuAN_ә5=-jWՕY5iWZװ~ej_ n=lSg55hٷ mBc.A}g׺صmrNmqktsv^mkp{{1hVshjqs3vϛV8Ym7k^p\-/8c@C"%_u'9.bke8mLVƅ\<bΔ}eH/A`bk*ʆn"|o[ׂx6DlE:uǮW<;FUoL^"5}1OW~uaYϞX|mO[Wh/X+6>ؑ#?_~nY^O/}n?mO}~>*ӿRI!҂?Rs@(C .T(S6SHnIMWTqn<:G <,]xj ƴ `6nР zGC =B ZHE!&.!6>!FN!V^!fEEEFņMMHޅ1tI>5D+,Dw]A'8R4CH\!,Baa *EuI3 "*"v=!EbRL5TA\bA,E,c9c:A?S dAA2dB8"&|&St>, fy9eUhfcř5\&]{@]UIJ@_ZZ@Νzƅ"ř9wB(Z\VJVelu(bf vTFr(ā(bܝPx` \RV@UtM^P: /څgB%]DA@exS|AtiA|BiY"D.iB^@*Ŝ%lPf$B)Y*R(&'Y)zF*"@jĥ&jgT 7j!=@]A&#%N]a"t&oj*jBkj kQ몢FkJ+*_AlljkΤ$gkQXAxN2D>f]!N+%xFEhHà^Csf^Ne(٧7R5fWF7.TlCRZQh^4$lTlH,6FcƞZ*ˊ&|R("Dɞl,v"BЦtkAAPacB*gd"+LRD,lL!mS۲EܮTݦEn--..%.6OD-I9.VI@./2n.~cqnE [u.BT]鐲V̮햜ҮV.-ZW..@b/&oCoI)>=/V^/fn/v~/ZZ/+BF`_C. cֲo/ծ^rN`O`-L:*ƨ$,CTWV-4N/('DTh/Z𼢰QLj000TpҰ KV 0uqm?&jd.I+B? ef-CLS@<`0 W* cEǫS110qwq1RC[%uab0!!pT#滺0#d$ E/fD&gi%/Gz` v>WYxxFk}.q/h/p1` r&00)0.1'33.2."E1334c Zs3GQȨXeTWX>/h2C/Wv3UE h"'-a/D# /G@t cm!rt02*O*D,qnjA0*-g#C3f oKVw[2HqH{Kt@q4!4Ogt!q_FkQ&p'#EFtAqǴLOV_A_ w5ED)WV#ZSzlC͚lբm730Y\`EaaSb/c7c?6dGdO6eWe_6f;n `_Tf6fiߟh@ivRmv6Z6km_CgnǠk'GjY6m )`q[l+sTj/sOQmv_ugvo7www7xx7yCD/Q:Hz9w{:)7 }0ѨҌط,7+7*#7w7Y/0Iɍ-Þ2 |I#A8|˄Wx]xa4B}xA"aQPя8ĉ'ub;R\3G`{x8+я9{-@C!)R8 49rO_{d9 1 #.G.y.D|`6l&dBg{๞"OӌIǙ,{#wx%ϟ&S'Ç+ G(<TӯKفB% ϭ98ЈgQ z%}Q5 -{{ ; Q7CG:5ѴW{\;|}zG R߇OK'p0x.Ĺ1 !c¡cB\' GS{px ({;c{뺪;{ |L8{0P{( ) )|x{ Q ʓ|{ͷ  x;;w'|<K0}ӣ{~{{)0G&(B{(ہڷʡQT{K͇}O{|~{[ɷ'еO Q({'֟[=o "8 >Pj>!ؾ{H>z3˾r>{=s3_%|C}B.B!2~ہ"pC{ 7l7d OS ~ A>@8`A;]eЎ7J)#xC1fcG"8FEN$eA%v繊PС HҢJ92hʕ5 IT3B oډhM>=(ZjjGen$Zkо C9 =;Uځrᦅ ʅvj`T6.cMONm2˙7߂YŰ%-5궫;Z l LvNN>.Sw!C&a TM%/K ֵs7u`sw{2J @KA:tBS/lj'goq7]w)˙;o2ڑKւ\'hqgmn<mګt5R%x _$.!LT%:98(#2A)H!aHx \ B-!LXuP# 7vP#< njxD%QbbD(mB,:q0qd,41llAE|(< J{A@ RXHE.t#!IIN%1IMn'AJQ4)QJUt+aKYΒ-qtd/J\41Le.t'K0Mmn7Nq25ǙNut;HsՔ<L}6PT6 {>sA uhCQF$:' 3B0F;QNȣ)IEғJ\><Ҙf0)MaRԦ|OQu5T.MuSijBUUUr18ҏne5YњVmu[WΕu]W}_XT5X.c!YFe1f]f=Z5mhK{Zb6u-e_&򰲵-d[{[4f\ ,q{{\&H,{ZFf00tkZW%g{^ΦWmmی"b*`O1@8q}7e{ (AA a"80n/Z g8V=X8@ŰH>wS."81*%ˋd*L쏝l(wʗ5``BA-,Rbn d&If3Uv9x`n,)9|v|M?ͳ>-3Ehxj.2_:mgO9<55N=STL_mVzvuG`kYy;EZ׵59dֱsV {ζj^6Op3ux{,Nnjn g7Jk~f!^Si˃.tAu׹X^t h}#'״;miL3wN7<߶~s9g^4vo:կ٬k}('SZi5Nz t@ $z.vm;2 O1 6u{xg~?j?v=~O__O/|?O;}/0p 0@@0%p)p~ 9=)7IMYI]a01aeq0ul} 0 0 0 p 0 p 0 p ɰ 0 O  K pdpb!8hXF> 0@u :¾#Rv$#a n;BA>6o> cQFv& 9B.ZaP#rQeFj#p#tH6C)A 8Ū1XF1#.`Neanb$1QJP#P!iQIrQQY`!8"`1/A8BqP \$Ȍ#ά#&ı#,pΑ#e&'ـ$z`?(q*#z!8"z*w(f"l)&#e hr*=$Z0X  -#q2';i'9B0S:B!1Ӓ&|a l)-l$mR-1$*#Pa Di ’,a,g.i)31RaiP'!<dӼ 07 L^v):wO1a벬? :7m*)!NNN=75!#.#SF/sӜs`c*Ҙi%a#n-?/S2?9*3#F(&9B#f'79b2#N:"4EI6K5_s"a2 #9GRDD/I0`QE#lsKTMT` vTд8bM4Q#H{Hu tA@9IaJS"=u,kIL9L#Ow#5U48$Tm0dX/#2C< S)QX9b S2m]|Y$FT 9 N35G:Ik1"es]iIWW="BB#$/'6S_ժ "` `6avaaa!6b%vb)b-boD!XXC1q16eAC CQTeuVYVSsUFF&Dw6iPc`@ S4EA.3/sVi3/.;IDSA&t9naCA"?J=nY/x{IhI#ԕSmDA5cX 6KYU=BTL09T UǗ& לʙSun fdȌRRU$rx!=F)ϔUѹ?Pi#1Yœ^xwZ}TPx/wZm$UWO)?\\3T{oox՞9(rU'Uz:zɺ:պW0p _(z߉< q躯 Z Kn=گ K[Nx)0 K-kK 7=q).9T۴a;e{imq;u{ P~{L(ې[ۍɷ[{#;R([Sºnxdo;>ڻ,=tb/fsLq ct.x$#X/ :!  dG #va rA;rt*e&ZE" pl!QȇExǁ<ȅ|ȉȍȑ<ɕ|əɝɡ<ʥ|ʩʭʱ<˵|˹˽<|ɼ<|ټ<|<|F۪=E}}J0@΁9=:ԡa"=L՝B]֓0Q) !,$%&''''((&"   " !""""## %'+34556552-)''('#$%((*+*,,+--)/+)2')9"(@(E(E(E(E'E'E'E'E'E'F(F(F(G(I)I(J(K(J*J.H2H5K8M:N"d"CA=e ˘3kޜU }r-i˖ uY߾f̳T9ͻM߲}i7 Qr}j럒WvytŁOek~Z*?6>w?ϿخHIEZnEE_5F8ۍ~]Ua=`xYȐt[!Qr"h8x}ȥ#\-.(>|l U(WadpNZGa釥\v`܏xֈնpїz%| Ia|w]%7M碌6!Z(Bj饘f)RY禠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯oܯ L :d 'H J`5 !A(R&La@t! g?!w> ()<"5>XC+oHT"=-Ћ?"1(1~^p m_$!%ou4# 5э#H4Mc闂. 0_ 2$蘄-BdW"(EyQ4,#*U&\`,UY ^XZ Z@H$ ~D1͈K]n0 ~K1 . Im Ye6x'?JҞ-}~П"'@'Ёޯ"Wu 0} E1岡D \\!;:Rs5)Gy>K +@q~ PƄШLDjjT&ԩťڿ-+I3ӭvSU9Mi$(ф10j醲f3[XA.0%( TV[b(Τc ],er]fvVlfA{Jj7[آna;[~loo ખMp㾖}.m["Ӎ+[bҭwq.uQ;\jq U} +IJUx+z}cPG&]fb]WFo Ww@nm a@ $ T2ָPhcx4 V̭So=J>+cZRyEhE Vyc g~mY9AM#ݔd:7y)WP*s^~YyfMzSF T%JN;ͮ*EJR+klA 1ʯ>5Xըj+5$"eM?-`IڤtLvI~z~e=mYV;vomnw;,tk8nrN nv>軛mx+ާwnf{WM[z>7#&ޑX*G.ACk|!/?]rX<*_ESsc(\\Gw.ra7QnNEJ;w^ `| _2h(C"`zԫO=K׷O]~*{(ķ}{|$>A/^OxÝ׼]  o_]BwG~}A7u4tR~\7w7>8~~yuJ}7/ $X&x(*Ђ.04ӗ1'S>ht:)0<؃>H1v/pDXsHH 0_rXZF&1H/:d][؅^!TDEggl8fr7zr;t؂ )iӇހã(<;Xy$؉w8X'X77؋ȋX ƸH؉8Xؘ˸X؇r/|`B}ȍ8Hx899J7v: ٍiYxɉؑiy!ً 9HDŽ 7X0357i9;ɓ=?I'| A2yMYO9Q SiKɔW9YCS ɒC7Csg||#za{mkYoɖzq9rYot)n vq yiqI{~9Iy9ٗɗIyəYYf陡9yK)9ɚǩɛʼng|q|ǜ ЩyIhy7ٜę¹蹜{|yiɞSaq Y!)Iiɠ S(9ti*ڏ!z#:[i'*)ʑ7q쨒%3Z5*79*+:;?jAj=*GʕIKJMjOQ*S*UhWڀ[]s_ arcJe a:Xxj og_h] PZxjs꧁H][J4W(=UTWVvvkAAUvZWZqHfG.'A`!*+@Iu-kuUaAH0Hq0 [1^1I:^ ڬPM`J>t^@zꮙJN:gLGK:!;E Zk  HNJ)n^p:l p3 t8 q ZШ`{9wK{K}k:v++lKi4u{@TXzǶF]`qr v6M=KǻWZu;;rHX:뼭kqK0M`qK.BËv{#ܔES(PUWtKL@ӷSr˹qLK+|[KQ!Z˳KAz:׭ک嚩%:@=K*棰RK,̵.I!4<%-;3UILL[1XfQ<*t`ê>hF0`mKDHn0e\xo\|L+0 SULJK@GJ1Jɠʢ<ʤ\ʦ|юʼqc`\; \˸s˺L3qqq˻Q|9)i̵qL;T;ڼ<|-cPsSǴ*,̾, ˰)q5k;ǿrا+ ;[9LxK,{Z;{ў , M҇%ۻ G+r;;DP ݾ4=,TK={#+v%I],@Lz; F;+,w1wFyȓL, 0րs2z|v}9}|ז#؆Mؕc؂ؔ؁#m]21|ʡvkׁs=}xyx7Q{7w }*Muټ=h)tU>hȽ~Cqŝ~}7{vJvthݷ-]׽ޢ"MHMڭjUHuu6mXbQDu.T ~J xy~)́M/'|'WT n/N|(̼:ȗ<*L;n@ٝ$ߋ}mxʽy={j{M2(^"nt ۿ g~==NH1ݗaw+W۸MEI =Ȁ=-ڵ\_}q垝} i1ؑ8#0^봾 뺾컎^~nĞ.>~پ>^^@N^~O/~ _ ؀ؐ i` 0Ān P @률 "/ . ^`>? u# %)n P >5O!B@ h0@ Pgk Q O'S_4o[??ceqnh/ o @*RO2?X^?a=kN _ @n _do_տnoJ~?-1Oʿv? o @u?J'B \%f̈́sPB](~61@;Ӧ9Xʈ#]JL5#9C% UG4Le(]ҕHF~ :GCA)@4KaEPPEItXP85P5lY(Qb:(ԤC|R+*_0%wPU/֖[>0`i$,]zk , +P_v켴#24}Nԑƣ'wyzj*f\1vş_~0{-zF  'Z*˫mB /l:<1ēP<9( 7L!= *K%{J+0&e z'!Qji!$q"@APa$9*lҡh*L+G Q G.6 eΌLt1&P55KKtt>; r+t<$ECDMl2J#MlR9O<>L=ON%5CST`³:QҌ\EKcP7jɌLn2%4v r)/[o5;=ͪ_qRڃ=0k[˰GH#S̠OŃJ!l6U7U: _|R̂ml~GGS2C!7܉$Pnt1㏣EF.R^eGWYfRP,ERz㌺ݐ&䗘^iS0k~9jV딀X,F6%MR涥L!'?n;PVx[<:uL5| RZ|a+5oݷ `S"Lf+dE2e[A'|t|/W_+wS{z'%VpGJģ O>Z:Uk"CY?jz9ODMCaU OP6A% %Im*dCLEΈH>%/lM\_H$}S> wa]ǰZB݃-^QV|,FqoHlj$d@݊Qb z3qDyOlc&G<iH> ?iP_FFAp&BJ@YfH-6RT2|XS%bjMf&r'钗e&Ls$2;3C)m9\%7Gm䬏9ov"P3κ;YygOS"ꪐ,%z~ӟ* x>)7NK̬$Ѓ.!L{q8YP /Hy؂axPg֩K ``d$6R<qA$Bct0MH @H@=#S2Ś*C2lYNÖe3CQ3gġX' Vrx3"A!x"cֿ%Bƞw n!/O7-q}S_ 83^ NC,[&'8Sd.g8q7*ps?zЅ>GGzҕt/O:ћuWSzֵ.w?U/.u =kw}uw3`}Ow7<} _忞y͓zѣy'類zճ 8n~לWqk.`?؍| x}S˷峯uoOVGo^'>/>G?6cЇ%҇ @DlTd9@@@t @ @@@4A <\dAALAAAA AԹ"$ dB%'lCB*B\$ B-0B#14t3(TC,C,B@B1D.C|C< HǾ+k;uȃD=HdsȈ;$zL{$ȉȅȍȐ|G{tG4IHI|ثy@yE4;T>IlG;t>N>JtII G>F93h$ʤɢjԹ#&ſ LL/@CPzO(E%Q*?0}:3 F1=ҶJ<<<4Q2?:-F5]Q;?<%Dz99OSbS޳RCSONRcm0CBDTLE?Ԛ\lUl޴MNS]6NO .UUޭ 'B^VnP6\en20]N_`C%8%Ne/"nh~e5_h` ejUJX(]ܚUHkyz^[mb8]6㥉<&qU(8h{[6X$b_q&hK&e n\g6FVfv闆阖陦隶&i]m0v꧆jꪶj6`PfFk .Q]ǭv븖k ޺Zw욝<ۣ"_d.^.`fH@ئe%_ Fo^+:8U`j+0l>o.SnU2feWb~`)Wq __q>zU("FbN_.&iVa7(4"흀^+#gVc+b+n:ppc:c^9.9G䙐dJdQhMAd*c\9c;G>'JB39힆96>>+ OZ~\ ('Xm^Z[n/gX_9q jIJ(bcFf^UgqFark+_vegvfivrPp_^s,o!Ű}6eb꞉^BhlxbE6{>ހ NWUcxY'7GWgwyIEc5>yt>{ܙ7Хw!6ozy 3料dwf7{zzzjУH{͕ N{ w{{{7w|  lɏ]|{|/wԟބgkz7]{%zşz/}|/~~F_7GWgw/,h „W)l!Ĉ2h"ƌ+jF"GjH$JVldB)gΔIH8wvE@>J(BHTtӧ;_ReM)xaT*؅a Jvٰf\6eEb֐J1뷈 _oUfZqJtI8ŖfW`~1J[SCzsFװ{*6FKa~ttّ#ƕ'/|wPB1B[Ux@~8 |g?Fy|N?E_%5nxP/kvD5`  ZԝAAVz.7@R̳"G1(bHR7~H9裎́ yHI :a8%<E>4\)$1Y%8 (WRfAOK@1gPI'Kv٧|z畃*hڹgN*J)jzfiVʨf)*Ci*묶Ҋkh*,k*밻+"{mQa@xX oFdn+R{p{ƋK|Y$.]T/.AL6aPof^{x|@q _($g2J$A4.S1$0lV#_65ӴE]TWE@t_1"[5WQ/q]B460z5,vrLeUYmՎWG'Kd6}C8nqe uK^s@X`Q-sbA_`?[NT{;lf{klD[;~/F{Gۻ_&e?z˾??(< 2| #( R % f C(&5SU 1cMYYӺֶ5s]Ǽ5-l`z>6y]d3^-meO־5jg{nv-ne>wÍuZ~7 yOV􆷽| pn-exРK/hk|8AײC&ɢA.as5Isj|:yr[*O9` Mӝui|걮e3޺O.[#z`/{ykSv*}t:ح=}ǹq@ţC#H<x3N|KyX^ƛ_\^?ЏvRSozГ_=Oӫ}zd^=]?1ѷ[g}?ǽ#~_7_|@| -^_YZ_J^jye 1A`J^._ Ml- 5] ڥ] ]\\1 `C 9!AI!QaYa!iq!6\)y!ƙašĩñ!aᾕ[ɡ!ba b!""\*9"Ib#R"Yb%bbi%rb"zbA⺑̉ա⹱ͩ֡"#",b--b"."/z[0R1#2F2S33Z4&%JCD\tggE1if? Ch5X868kW:c \ hJ8#U9#;?6?DZz'yKx%y '}ZX:g 'gUyz^V{g[B}ntjh lZ#n腱?'mIԌXxƚW@fؐYdhڍ樏 h .Lz!2iJ)P) ZiK$6)VVX2@gXK)iivy(]BE1਌/#)֙:*9mfy*(W&] eKJ`jn*Kt ^*.djkJj BjjtrĪ%ѥtCxeErAXAٚA*d* x]Jd&y+#kL'כٰ~++|jpvkU`kQl`BZ g(eIJ+%d!0 1@U.FN.V^.fn.v.ZJ, x.(ZhUꮮC.K|JnbnX<./f/://NN2U.Qm o: oB*`Ѯ)oF*Ue!<^km0/3373?34G4O35W5_36g6g R77R88_s 69}.w>)w³020n<>>3ֳa}3A@g2CpDKT0>is>tB3mGo40G`4~KS/bt?۴(M,'Ep=GpJIu43RHSGK5nEuA)Ow_iSKoO3@ aEMW?p9P55X5Wg\[׵_4wS^_O4L [sX]-"72 5 Grd1>6edcUd`!*g/ }6 vfcC2j'OE p<+tu t۶N nOo`? WevBc5`v Cp߶r+E4nR?wvOSwq[xS0uvxoz#Q_t}wsMõ ]tpӵ[t[>oCg4D 4;p_/uUQVC[LwtsvKRG҉CRيo8Ǹ8׸OxcOBx (#ZHΑG9L9_3@'>T@>  'S@HBD4ӏAuԁAB7+?f$k{ր (>C >wMߟȕ>AdsI3\?D"D_}л¼?d%>@ ֬&L(f?P:H)~cGAv\up )%\c S&8J3n gτ#DpeK+;δY&F>FIP,]c 2%.=HA8)tUQx&29DС$ŋP"@418*xPrM$sgϜ1r$ҞSB~X9Ea?\5vm۫$3yK/~X+*#dFBW;ԡDNuַ~Uu?LǪ!a'+Gwkn<1`e1*AKh/? 3<=zYr=P*9EQīJ(= !_o YhqS ,XטX#9:J(K$1$̰"T=`8K <(#PEtBI;oG:l">rATFB%Uq͔8! yG&OB_c 0r}Vn=^bRh^jBayZل XZm&rAێi \ΥWGOj[ym=݇H!}Z2LC$s1 6|1fWo YwYۓX]57F <^^ aD,9>ZQ¨!:9B*19$$Am/Jbk;EN{m*mQ(|mBgq:u9H+:@A|IzKK8w35N(c8975j' k頀!Cyr l~;wZYbb: f9b9gISڊ3")! w4y W6P{<`UujاD?  fP] 3Ĉ_~`Abq! Aۚ Ugp$K CIK^;*z T _' {841@_ ф8ls|f'3V2?* dyH%2NT@RH'=r\t$%HLLW'==֦\)JTkVSe+GJXF-Ky6%$YZ'$drj3<^Ɨl5ikjS*ors,g2Nut;OyΓ=O}?P5AP. uC!QNE1ќa vT%5IQR-M(`S,imnRsezSE5QT^ԧ˩mNuJUjPa Tn]WVw>5 U*ժUXiWΕM];ST}^ZV 5l]WV"ucW6#:0},g/V6lgE Zώ65jS Y*mq[K]kZ Jq\.7ݣs 2WڐTQyLw۽d%kxћ^u{_Η}_`6`/ h/  Aެoשnub'NC7{׷ǝ^ݵw}w{w]{:G S!ڠ n^Y|~yO]z=ПGzMhu&}L~_G}~{ܫ~[} {}/WGoԅJѧk}ejyhӧ>Ϗ~o@lf3?'}n~>K Pjp- 6P0i#?p6*AK00_5c*MPWaFO'pwUWpc0ah}pQP0P p y0N} Y0 ]S  m P  q Ӑ P P1 PpPp qXO>BQGqJ1NS1мZqn`6T`Ȳ,lʪ-tr' V 8Mj @X˨LV1UV2306+W 6 NR/2g@fC@SfRኳ#?,9Ӎحݞ1J15ȭ7sӞӤm06 f#`=۳st66ӥ#r6->9M,:@1cJA``AyVZ`.2Jv6<tj#=דf  =s& HDrCSⴡx*:6ڦIԟ AaĐ,ңT`R.) TSII@NNb`J+À$ 2hFH*N 4TL4 JuJ?=4@0 J :gV:W,606H9!mF Tѳ5W=g# 6T@V}6Bm<#qZ6@WiTQT1-rtZ~VRWWs[nTk#TsU+Y`u6/mlxA*T^~`J%# AaWB43MN[ '7Bi6n][` \)S6^7C12{HnUrTs6XF@FcGdkcR ֕6vF0b^Uk$6lvlɶll6mvmٶmm6nvn֧nV#Y+rز6p2cJ Iˬ8#wT1/E6H307qTz뼀 KrqoNUWUsf<9MݼUt7nKJrO38` @]6h7.vJL9lG7_وFϙӺ>s=,[ Æ6n(ttvyy m3y9ZA&@]_zAc'gB-ty34g=UE_4Fg~)˨@tFԣKDڢ2zrREDZ`HiqQKjRWO1u}}bj|˗NRTk8AqlN՗дJה bJP P P'wN:OשQ)|%R1{O pS຤ C66hL͞[9>V6$ Xg#YkWyV{[f;;_mYJpVXV[˕6v Vb;؀eҜ{5k㞇5]kmgC` `ub=v6g+v.v-h66d_6fiCjzVoVHZNgmlŋe1I1UI--c&VXi#iZx`6RjiBjK٨[v˹<|ɼ<|ټmu\OH;vB3ErMئz M7{HCučZϩJ{KW'*uwKXnk˸NCyc4KVyw}פqIk׃oqS qY%zWa3݄{=~݄tA]KY*]nqه =~oRew WwW=3=罥-e5ݧ}rJؕ&ا]Xۏ}*}3ޥ&_N/~!Rջݡ:~wOK [cΝUgI_Gݰ]Ϧ属~빾>~=©^Ҿ'ɚ>⾙~#>_@IҾ~9B'({bZ_*$ka~`t&A{ߖVb#dH~d.(^^3`.AD߄n?!l7a){tOlB| h"D #`hag, Ϗ <0…W1|1ĉV1F2PLlUIh@G2Z$a?8H*b6Ncȑ?DZSp0ZD \SQ}ZM֭; 1 m,jI\37Uz 15POY>|0ņ6PHI?23hGJ- ŜUhfJ"8g*uLU:h̀L`,o~b:n:15\Dz(ezPY&bh&Ao&ti*uz♦fhi(p hFRQpW1 9b# y(J'.*kgZ뭮kFjQ)HaDPn14uI+)8.Įz & Q UK>HZ0'zY?2R-X X Q!؝_<,Ds,[N.gěoTмW!\㼲?d=v1Rz] !y0 Y?btBZcfԉ8hSC$Rb퓅;o` ng]bR?ˏ0|%sFc3F^&X =:0XLr9xoÝ63x(~>hM]"]n-[?$ٌ1{3Ya kb:j F<"%h>ƐB \ %Q'A D A0'd!\M\Y0ְ$HWYKqC$MxD0KPr,)OT P+_ XxІ6`V⅕\%+wK]꥗y,I`n 1)LdJs\E0Le百lf7yjᤒ7 sR$:r h`lv3uLxt2wT,CIn>'4˳ЋB5B RrDHMjєR}8]B@4 )?7Sf+@KӠ  JR&CfCStJ?UqyFh>5w!NEIӔԭd_:Դsd+U8V ֩}%=ջ@,:1Ze1~m\!*b6ei%YՒvmW1W06kW֒-oGTVr Kxp9K2ڬ(J"3n;+ݕ)R݀ʷ$zZةekI\db+Ыv|GXҷv0IoZXle^+\IFX&17E6!M9 TUQ0s Q55@_$98p .Oz%ky\>m 0yd.ό4yln 8ytEp%|-w)B!j.18r k~U}6bs =Gy^L@`V*_Ge@Yyv0@;/0Ԃv.hx!@JU0:Py QlW^^wӜyN}?]?ݔr;>f? &Sr'G0pV*; @t])숡.'{}15N[:vqrlh[F-$vjlwaxw|z7x6p}Vɶlrȉ艈thHhȊ芯(]CFdȋv(xhLjŘȌWxMHըֈȍUEhCV税ӸXh݈ȏG Ig 2v Ȑ iIhyȑ(#I%i')+ɒ-/ 1)3I5i79;ɓ=? A)CIEiGIKɔMO Q)SIUiWY[ɕ]_ a)cIeigikɖmo q)sIuiwy{ɗ} )IiyԀހ u Ӱ9M9)t P)J9X)IyИH ۀI)Hـ9M!,  !%&'''''''''')* * ) ) + .2555553/*((.!6%@&F&I'L'M(L*L,K.I-H+H(F(F'E'E'E'E'E 'D"(4$))&+!)-+. ,/'-0*00020611<.2A,3E,4H-5L,5O,5P+6Q'8T!>Y?^@`Ba$Lg&Tr*Xw-Xw4Uv4Rr5Qo6Kj9Dh:@f<>e@?eHEeMGcTI`VK^VM^YQ]YU\ZX[YWYUTUPRPLNIILBMJ=IH5FJ0GJ+GK#MS&U\+Zb-]g0_j1bm2do/gr(hr$hs"hs%it-hv3_3M362222222222222222222222276LCdRmpYrf]tb^v`_{b`d[dShQlTp_shwf}ZTRPID>:889;=?A@@@@@???@@@?@ACEսHܯMްTd鼂¦ͷùwVSRMGB91059:x6r4q5o5i1 H*\ȰÇ#JHŋ3jȱG|Iɓ(S\ɲ˗0cʜI͛.Eb҉S#Ϟ@ JѣH*]`H?`ȪXjʵׯ`ö@fBJ0mA 2+ݻx"մQo`? s R;=⾘3k̹fCEXX`iҦk=[lh: >zd̻zrbԘl4+2LJa'tږ+]ӫ_0n֎;ENYdaGPiŃb7vu'Vhy.wj饘fJ!駠*ꨉjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/oܯ L :d 'H J`5 !A(R&La@t! g?!w> ()<"5PA 'B;OK !-r_X1тgġ5QQP!+G1 (҄<)D2,c0!Б~|2I=v '?9NR|d*NT>z(D.TDP& i@jQ6 D5RtJi)SV4'KmZ(+  lLFPlLP!4Ql_vl8Uy[ׇH1qoLL[ 7PwUVPML~8H)n 7@<@mVH7q(uM񁠼8&@JDq ߊ%79" N_A NksvG2 s`} [\?=,_lX?YWŘpzќf}~@q~ oW:#<](NzWQzηJ۴1U7ߞ i~)@Ʈk6C Br=̯_q;@6a.r877} ?_oŚH 3kfs6r~kffgvv~[lچ`7}?fg 8u؁BtFmjfqlvvU1Ieb&L&Vii y g>HxFF`QrOjLXQ8gwfH+&JXhV6i7}eXx{~8XR[<$m;?J;T?X x:8XHh:h(&wX:X6SË英(ChȌРc}X,bEfdV|%}xxW>󅎜whwE(A᎓w8_niigpm7@H1b YXȈxȎix>ّ$EyG#))ml XfWp/0i]h&iԒ)-99yM | H ɑ y`AY B(?ٕ$nGs&gsD`~9gWn9ɓW@VEcFVEIo{U `퓕CY*YQU, Eu[u1ofs guȖD ȇ JWYWZyWv>}U|9VIy1Lr)A{zG?w|{/zE9mйy)铕 _ZٔwaI~o)oChM7ɔ 托י= :!HnQ=j9y`Xd t[i9>T-yhsUhdhimy9Z,E!KMz1R:TZVzXآ$&p`:93ZåGQp*Uaq*t vzgzI衧{Zy:T*|j9b9\*-6ک:f: zڪj:ZJz ګZǚʺĺΚ:: pપ*z:Zڮffz* zZު; 00[f0  s mЭ l  l0 P Ȑ@ * ` KK !+\ 1`p.,kqp `p0RKS kP Zpi= +:E{+MOS[zYڵk^  @j˶fnBK`HKзjVZ˵k0v;+s{@+f@P[KjۺZ:2;8 ɐLҫbka+;_0l` gK ;˻*kx f pk ; @ J  VK` K Kk21;:f D|),ܻ07l4ë Tġ``l ¢zF,Ģf`LOLéoZ\b;s 0i\GLn \Ĩ:ǡkǨp\lɇ\ȣzl'*ď,3] 'mݼY\k02uᰍٟlDž5-~ߜ fl޻]N< ٚڧd-Np?nL0,5 | {^~; ;úǭ姪tnޢ^'-I m0:> >jZ-(,ȤJΧ>ઞ\B ɗ-+t.knʩN쯎颪ˎMlΰ3~2>牾~n~>>^^z{.~n'ňf˰U m mzw>f0:l`86]޺p֪L쮎O| Eoz-` K`KҒ҄xNW:#9+Kf{/H}UGX_t?]lidgc;ovmM%ғMcQOOdJң Z}ǎ:3:ȳۜ}oǎOJBO<śѺG׽+#~яR ݏ9MvP Z @^ o`? 1P!|)`6 bk@6Z d@.HO@}V#`RKL… {(( jl I(C4S򤙔7[9L͛szhK:#=5uj֪\Īf`Ifٳ;{ySnQέn=ZPRW$A]*K0‡W 5tIpč=|_V=4ffl 63g₽6ƁdĭnAc_|LLy4>9~+3Lep='x{A="eRA&8#N%@C[ c =b QY~)x(H42czpV PdPB]F-Fdp,kYHt"yC&I'οR_ L3u,t/̧O30qs8Ae<h,]O&4 'SeQ$95VYیSZ4 Qp sP2$Ŭ 3LY[YV5"@5ȡ}VʛtMpP3x)kXd }>y rx޲՗_&FNza=+xbE#/8c?c-wއGF,Se{9cvxfs֙_{dyO⟋^h]'Vi8Vkgh}zzlf͆l;S{mn/nLo\! B n ҡ &j#&Pb%6x(+-L|*qZc8FE g4E8noרF:q&G:4d>ґl#! ;>Rb+HNN򒑴"))N|&=)Uqod).2v$%-JY2\-9JXⲘ'}YdRȬe.ocҚ&Lc޴f8AvP u{'Yy#)wD '@꾁g:_3,HfTAPq(Hq& J Aw.LקԀ6Nsӿɔy,)OB*RT 2N5'ORFթF_U3ZU&ORTvu_5+кVbְծukY t]WyNf0V-,`*V)&Yvֳ8cm f/KȲvf=;[Jy iUTnoB-ߢTmluϭ,t[VLU׺ιv׻+ri\f0 M@r|(_*W&8YS^ӽdoff9"XVoz^ؿϼz3,ro:8/8ޅqu!LL"q]d1|dL|5i<RDNW|\֖v) JtVgB@F g2ΰU),Q5mn t+tf R=Wc&R:e6fMHyҒ6LozΧs O{ڤUiVԊMLsv=lbFvlf7φv=mjWvezw=nrFwսnvF1ԧ7o~x>pݡ)Bap7x%>qW\vS5qwyE^yUr/9IK7yus7yЅ>tGwhLr Ozԥ>2յuw_{>vgG{վvo{>ww{w|Y'bqu77G25`P|T Y+ ,}@EzÇZǃ8@p5#||=tHȅ W^>E O+ VO" aA.X?/Ed姾>9|ŏe)c{`>y<@ @`CH@L0@x?C8 (@TW@` }@@@l7tr=0?\@lY(9D$ 8lB$@iAv03z8^Paի,Ģ}Ђ*>C9%;7h@LD;$`CD(D AӛJDt!;?E$?R>k>+[LYDEU}L}؇;K4c̶]<@ٓ=?Ll@]yIxim3CwǨ+<z{|}~Ȁȁ$Ȃ4ȃDȄTȅ,7?k8ȉHtÊȍHhȐɑ,6ĄpTɕdIɘ\IɚɉD94ɝɂ7L`ʡ$ʢ4ʣDʤTʥdʦtʧʨʩʪʇǫʇӛ~t?T`ʻt=3l`ʺH|ˌL72\м؛tғNd>C{@>L> ԇ,xDMfS̿LMtCD lx؄M=LL==Hkﻶ NNMρNjL=H4DS۔OeK8OO\ճPdOktOG,Q$ NKsr`,C$AzA-8@6 N mQsQP$@A$PNUDjO }Rπ[驷dB'4T5lC} u3-4[U EMFDXML ePP 6ѽ$ 7J5>MEZE ESOiM5Uu}`(^mqH\(R6 PSI^\=cXcYcZc[m!dd,GVdM.fI^5meWn@_@ba>fbf\b4eQށ6pcPh^Z\)X)Z)@gus^XgtwV\zy|f}}zhLXgx6hFh{N܃h~Vh~肦hNTh{芞hi=inh隖ihFi&h蟎finhNj&n^b[hVaNffܕvjj6jVr.xvvnNhjiDž_Lm[kc^dN)x\몶贆of0q.a8vfe*lmjT`^\4HfM^\~׎}^}:O8a͖-Nn^nn~ށ\V\,Ї(nGd\Zenoo op/p%˵8`p>p M.ZǍx`,s^_ a]a\Ocqmqr!]"/r#\$Or%w^&or'e&g*r>rr_-_+oa/r r*r/*E5^7^8o^9õ;>(xyjw-$Ywzd (Pջ6-Sdwފ 5 yG5+/}w. SӓvRyI8R!˱;1)k1)z%("'zwz{Pzgzoz_({g{|0zsǷ{ϯ71×|W|1.6&s&G|??y/ӠO}0/ӟ'2"5wwǛǛ@‘Bw4-[3vnv:'4֞W!E-,'z&̚1krb`AL2l!Ĉ'Rh‹7raƏ"G$ʔ(Ol˘2'i3B$~5@>8S,ii͕Qd*֬5!ncׯb/kVbٳj]۳pM;k+_ꝩU|NL~M-#KNIrUTCHӨɮܺ먪c3Źeo!w |[n9ҧSn:ڷs;Ǔ/o<׳o=ӯo>?L3V1cJ * : J8!C(Lz!!8"%8᜸"-"18#zH)O=#>$EY$B~$$QJمbʁSbdGrI_Jjy&ixU&q9'uW2CΝ}' 8Π*(:(J:)Zz)j)z):*z***J֊׭imA6C)nS>],(.`Y3W0R`.?bB+-Y*S.Y,N[Zm'/6/&H( Z/&+p@snX= WVUY,r} O(g(,p̮Uh>Lh  i1`|-`-0geVX? βtº6UQO]VX[5 "q+H`5՘MuW wqtޘ0V{=߾^5s&-zu[9I$2+IXB.c9&Zc??؄)'1&#W?3*n-kuU/8/~UoVY< jpoؗ8&_?WG+O~P`<6+ AןQg~۟; !QѮA)BpYyͲ>}]A,ְypn! Hˆ,>CPn|VFX¥@;ܡ;$F4$ az/ DhZ:?e g}@.1^T5)GcDMaTH2} Ӝ>n?%St/bVń?U+b5(ŠNU+UUY!!D1+wEZ긯6uJrش묔O՘S|D:Θ-3+^1+`k򉁄 z2 %0v)=Oz1ؠ i;Qp[}g ܉r \L0׷B\ו+rGT̊tsF^V,Jz2c=}:brt Nvܫ'U⋉x.օwl% ?;08&>1S.~1c,Ӹ61s>1~!ȱr0LN~27dQ2($3dhʇ!1wZ>/즛 lYs8ۙB>tÉE9V2#-Ih23MWHV G8-QԦ>5SUծ~5c-YӺֶ5s-È/򵋀ca> U!_F)dfIgzESYdLc͟k?mkvU\bߝ{?#^xqZ|,p?g`s?UJ=l;!,goKmf[)gni;ܗWt2_\Roq6Ǡ*'GNq[M @Oh55&)[B٣7|2zn=sHyrAoì9t\ 8g|B^3vvxY48^)"^'Xm̆8P_/{rm/^tZ0ީ_@Z} Xm`}̍[f\1Q^՟ ѕIIMV@Ye茜DZ!A8*-Ye\D(v[Z M] Jևls G EmXHX!ơ!֡!!E@%۰!!"`E"#6 bM#N"% A_ޏyby'I%$fb"-*^+V,b!"".`/bAh"01#2&2.#363>#4F4N5^#6^#&d#7v7~#88#9z6#::c:#;#C\ =C?`p5Rp'`%8 u&lZccOzFrePP72Sg6v'_<8L$6AO|'-Oy-|5X (7n^ng=>9~ @^Z#ewkn&Kvb&c#-6'VfzhNh6g揾hҨZcr'?H&7Bnx8~f#H'`5ftf>)_J6bdV7f&z^iNfM6=>6~#@)|^)5)f-%7>5Cbh7~jd5訖jj-8Z#)j^ ejjkZ㱒'6F+72:(jZgv#>#(7X#,iJ«)\Zcj7B zg*>:kr(|dZf8i+RcfCj_nVkFzz,,dC5e~㌶*̎,B4^#j,_̖&W -ѪѪ5,=-զj>/X%nA=C<7)\:A25P>XCm-6.Xfc*l6b+J.ͮ!CւBǞBMFLA:6"R2RJ:`:H`c^U o /onn-6R.¨5/&fo.j/heeHkf.Hor#Ɇ5/v.w5ъʲ/辬Zί뮮7R0/og/KFv.-Z#?? .7+6nB*oi+᪥o/`EV,Zvps/G1¯O"I5r16k0O7͞orIonr.ot5}*fc gc=(ɒ,nf.^c*52bl+{c${#%%crJ +$ӂ$."\2Кrz*s+C-)?r,2f7~=xl66&fc>iZ",3n&c 23+."6r:5rgccdX:j+ʦh*_Jj%gRi< (=-e>>/C>"_s8A++$4NrC?4%pm tR4/F/FCV&؂tγ:EF[#GG{mH5€3B CVA=-6Be™b#t:tF5ҩ&nbBPg7䃾s<PW;,F30C(ĵ&/PhAz1OrT.î4$5_o_Sw?.Q\cGd5^6ԮAejv,36pvx6eh#5fzNegllm6v6kPJgg۾S o&@>$gVsZcv'=5gf;r.767ЎM&/5纪`3o.CcG${c8wc? 7~F8S:JGZ<U +B~c~k>&P~+B0~9~~> ~KswLA[~>Sz?>>~'?~ >GO+g1/li~HWRC}۳'??@`D"H6 :4!A)2|b .$&IXrʑ%a OBDXgO?:hы;&UiRMFZTWVź+ծ_lWT/Y<ϊ ҵԱ}+9M!}2ʍ!/}2ɕ1ΟłshgTq}b_i6Mz7g25 =͝tfΞyg.\_Hl/oW//P0j=BP@& B /.C P3DD3%0$ QD_41gFo0 wPB!!$A#D@%}l?'b2+R-/ S1,3LS5l7S9;S=? TA 1L#BmG!TI)K L OA UQI-SQՓSOSmWaUYi5i[y_ Va׌rX%VemgԠ&ƉkVmi8"@n-sMWcIwWy{W}X .NXn!X){|3ژcc6?x-Y>xe,G LgiEv"$fC\> fabx@Prk_y搖:"S&Ev%i-GjJ禃pV$b!%cbk+[,I|=\\T(C D\ ̹F2>\v/Ϝ1Cȣ8imٞG<zRShbSьWp?&f讐j}XA:$7_015 - &_ID8L$A G5Oz3*9`LW)Mz#EhL->׹ 0Uq $tEwG<A$#b‚T,cL2AUD*ZশjucJ UwvRG<H)r"bpUi zjEya{SEt)%)w(ֶt2Z'40=`YI*ym"mfLf IPA  hc9ǐ$7 Hdm EXLG( G]RLHŕz(&C'U-t@}Z U;* ՟ERoDR4m"L1ɞXjBA [?A{lR~f2nմ&$&0 X/ A! nPOJƘU0IDQx- 5DB yL)]Sr2S? ab>ʁ}lR#ʥ@l}% st3 d<)Ey6$0bE52lIn+h$-xcI =mr&D$lyTZL`G.?])cUp^񎗼5yћ^u{_Η}]Uf  -[T }`I}uG7q4476cs6S/rS4s4`>G211q1!smHCtB w#Wrgt!WFR L-U+Q:EB-;Ac7<'2owg8f[UIpkp1EOKFGqixx7Cq33ne z!oL wPG=Igr=sy_A3z˳=@s!3SuT|M5HJ>r#?2$X7=2s^w`U>ɓ,~VNф oV`g-$zymaveY=AW؅17/y1%X8SA!"!$CG瘎x1"{=)tQяT{ԑ/+ҐْW3ғ?ٓ%@G9<19Sc]ӕ'C9eq9uyyJά]D 7pM@+_-YNC Ȏ༏9dO <˛ۆ!0Ze0@&8DZmCš7ƚDڬu&$z"NLi%'''B yjΆ@Z' ('"'FjBTcDexϫz15:v" ̭&T~r s[v~ߠ)b;5 㳩5HA~"w&'T;Xu"@"@@@";'' ڶúC)C{ ˜B#05 Q(J'n/j۹ "ػ%J(1B[ݻ7|749lO"|0ayۊ#~ VHF.'NkB5<){"`gG&@%\$<%DM#0Bɛ[bɡ$B_ȫb˱]"\6b6^'Ϊ-&(~uJZ N8C?>Å:լcFSɭ1I"B Ƨa=VW'7|-tr-ڱЫtҋ?N1!Ǥ]lbj;𐲢24~g6G`` AZz@D j+{̧ ~`vFVHeǢi"i!{%F(b]$+HlTę$h8HeV^e<"'i` ُj*lmOߜ Fe}zi-l/ z0%˒!59Z &XE"& ~`5 >pNj +z*y$E 5+XUmKnW Sk|9y h6`mښPnUXzY2+ jTT&k>M.p?Lo 4.h#v| ~J2la2 AA d xi- $˰er! qROM LD?i2dX(e+i>c4d'c7m>W`>%'4'.v.waBLѩuy՞zeP/2DROPbzKyP~8ݗ /TyA`4N}o}~O~柏~~Oߏ p,&0 lJp/ jp?p$, Op,l _p4 op< qD,$*qLl(JqT,jq\0qd,ψ4qll8qt??@@@@@@@@@@@@???@@BDHYvmWMLNPQST_ڐȾż̽whudmehoYtTzSSɏPјOƠLHEBAB><;h;@=3//012{2r1g}/^{,Z{(Xw'[s&[p&Vk$J`!+F)F)F(F'E'E'E(F)G,H/I0I-H)J(KH*\ȰÇ#JHŋ3jȱE?AzIɓ(S\ɲ˗0cʜI͛/ )2NuJѣH*]ʴӧ+{u(rjX,Ī*UV ͘Aa]˶۷pʝ[QjZRj޾g%V;lJL˘3P?f^L/Ǩya,ZƖ86 N8Ƃk$ryz*bxkѭYW]v⍫_Ͼf pCܟI[qmmv?i^zus-XE(VhaLhk xbX_aZFbx Mr%#kav1@g2歴)X$. җ, ""L'π^ ~O?rH'~ףqA0Mس_t=E[z)YhV(Ta+:qH)?xܔT#5+7ӥ{R;;ًߥf?{V iRc[ʎQIrm)!~pWĨoۿ^怕[iM#VۗR_գv[;wR< 歩t]IKW8[z i)HJwI77T(q8DŽ'}9;5hz_;)(O8jl؆np80tXtvz|؇~{Xxh؈(8x؇؉8XXP ȇHuXsxH(˜xȌҸX P x k (@ŀ th ` z y`٠ࠇ@ H 0؏ (b@戎8 bHt {z y 8 Š0pt( 倒P't (9@0) i)98is(H&*9,0)y2I8vx9H~) 锤yS U9/ItZ]as  yijɖJM9%9XI-WvٗsXbu(ɎyXE90 L9 ĐY57i3 Ơ P |b yy8 K9is sX =s0 s 0t E ۰x 00M  YY)p  p ٟѠ  }S0琢`ssH) 4z螸Ij9i!:!|/+.2JNp uȣDFڇILT ,:' 0P[ڥju8PzJhZom 08ʛHvʥYpBJ] XW0sHɒJs 00bZi|Dʡc z iI 0:ʠ JjJ !ʬJ : yأjzڇ倯y٢*:j*8 ЭHvhuXeȯϚۡ  ux9 *y(k ;stJI9sʲ걌ʱ5u!+*<;>+*y 帤Jʛ6۪㐔+Z2Kz~]ۯs8aъʫJ?{먓xFs Hyh鹞{]z I˘:檇 Jf뒞[@9)Jˇ;Zۺ~Fjh ` YûK 4ۡ+ Vy[txu8ۡ{[;;ۙ틞;y Ơz(kx۔ vIz˨/H8bk}uȵLiJZ*uH p 04\| PŌ P TlXV|YU!+Ě [1 +ĻKKk.Z͛O h@C`5b=۳8=bV:]_e[ֲ뼄 oM3)2m`wmdԉ }mY;؊ R܉cLfcMɦ퍗}pMHizآѸѠ #M ez { Akmu<ӳHM =Ɛ f J̡x s A]ǢQڡ,9ݖރ=}ϚsyajȚ;ݚ(. Ip,I>ڍ׍Ⴜs( Nuh"Z*=].802~]ɝnUݞ0`mJn&.u(|M|ܨ73 Y [NHzص( sF,;:AKM|둄^nΚ. +Pv0  y|: ^飮>1N>I~^;TIꦾȩ^:>^Y~h-).^uxv(Y<пO溹Υ詠W o`ii|Y厼rt/>zXI頸{۬Ȩȋ9;A/?O0l8JHPR?yH5XV]_a\?gmiCY7GOQowK{oT/Zeo?/p ?_/?¯__ɿүѪӟ_ @ݿ? h_@@ DР >QC-^Ę`E=~RHIDdJ-WIeL ™S„։̚E/5"RM2uuT*^]U@[vN9T@acu2.UsQֵ+3S{Ap`c˒=ITE7> cɅ+ rfYnVhҥM'^6A J\3kز[Mp6n۴s:vںo ͛0\yt㾥;:̭kWWtܿ9y={gO> Ď T@ko@cм L)Dn< !;oCJqIPE37MEEkpE'>ߥK~EXU_SL~bn}%]޳*2Ine\N Dc_8AGV @:i^Zdi~zMzM֙ qH64̶ m0FQ$CÞonn[N|> 'OmJrӜ=O&}l^1tUfܵ{% s]?>`{M|.dVuɨE)34YKk^h/|A xteI1;_k M3@-Ё qO3ra}W3׽(Ñ;a4(Ѕ/a e8CІ7auCЇ?b8D"шGDbD&6щOb8E*VъWbhE 8F2ьgDcոF6@8 7юwcG>яP#8HBҐDd" #%9IJVҒd&iғe(E9JRQ :+e*UJVҕ4ƾ XҖe.uR e09LbӘDf2Lf6әτf49MjVӚf6Mnvӛg89NrӜDg:؊"B9OzjlHx@b 0 0zXGi rHk,8`z7Fe"igJU$T ?@ FXb *xN#ah|*ԠC Pq#b@M3T-u4hQ5)I+kXRPK$HhэUt[[U#U s5VѠTElҁ0vД+?Q׸uW Usbi2Y-8~(A&ֶ-b[>6QnsU q+7wA8؀ 8hp[\DXqxHּ"YGcV-5i}ijK㍢V 8 HklIY5Ep^B½*~`$k}1nU5 'U,A !h|h7Q` qx|s4*2ZV~W$+Yz$" C6 L"#q KvwgFsռf6osنbgB:4DhF-%=iJї^{JwӕthLӧF5C0=:կuW}i=2ַughp!u]g4aԂ/lf؊nvg'zƶXmlw=nqF76=2݄\#fa$BP1OŲBaFx7iN3x rg7NܺSѦ%{!sxlѵkM/Ʈ|Vv5 C+iN53guUe';֯QDqOO}?Z#q~4@?xN}㏑B"u[4 nqq\pHn2iAJXj,ȒHK媿V<腓P> r.@QNȄ؄x(c/8 /=A2oa3>ӿ)rA B«ԼO3; A)\#Gzt{00(K1# 0ͻ4lAt*!C.0ӂ0D1+C+0ӂ P6ԓcB:3z-=71ط 2,2 82'-SST[:†[)16D+Ө&;2S4 9 [DI$?I8yCg|"dlvjklmnopq$zrTu<"M4-9vyrGz|!1,}l!ȁ$K(ȄLH*?tulyȉȊȋȌȍȎȏ 3zɑ$ɑԂDɔTɕdɖtɗI<ɘɚɛəɝɞIɠʠʡ4ʣITʥdɢdʧTJʩJʫzyIʮIʰlI˲DI4˴T˲d˶ ˷ˮ˹ʺK LDLȄM0ɼK$L4ÌJldLtǬJhX1Kdr?a.oV!vo?@D(f3aoz+0@dM'j;R𤹣%Iz$fpop pa?pD qwĚoD{ r"IrFy+rI:3[rEFH"NhqLm~'~1`^(+Ń;һѰ-/s2(dZs.r{ss,'9 E۽0o"377zct?[`zS>z"/uS@l's"gfz,,sw_/-܋BC228b^稾ksE&df'q.|6f4@_w|ͷj|+Z}}k(&_jWg T+jآA :@O꺮} , 4j:+ߩ $488,v"~]<7 Ϣx-~01~+R{p k*oشh׍ „ 2la J"Ąnq#Đ"G:dB 482gҤz$Pe[~ ъ$/6P myud .m4ԪWn-CBKPܒU-V .r T)^In5VnsBc!/eWSNj3*Hжk(k/CUK5lZxۜNa9?؈>§oxBv~C|7}&乛GP={y|T}6!}2Ax w .ؠ~'*grfX-Y} bBtǛBw(a$m@R$FDbԣLp7 `0#b7,"%HRi]nebH^&mf'uy'y'}' :(z(*(:(J:)Zz)j)z):*z*j *:+&+ ;,JP'dR,:,k';jj-&-~."Z.8I.^ɵr2r2oޛ 2[ JgsG;$Աnh=*6bsUM?{V@1hjۧ.{ow݇瞾{|8A D|҇?6%)!En%L d~s_&2| .9΂!4BY y'f!Hwrdш0H8ID|YFns lIL Ex& *`Ax1b$8ƄQh4F0qgc 7N{c6̣ G> ܣ̀CpMwI(+-$ p\(5@D"L@+xHFrt#$>uo wB-~2݄9`}#!j?%sfqstHH tI:te41HR@b7zS4=aöd 0 Okp{'pD ajst ﴵWMgZ?Kzf SRu (h>nɦ7 YӓT"۔)B6ŨA!vTIwi6ԥ4R]W֧5kf=+ZӪֵn}+\*׹ҵv+^׽#I3>UT<x찎},djm5,f]t$hC{CD@LiS+Ԫ}- IYj%mCJr "lۣVS=.r 'O%}.` xֽ.vr.x+񒷼=wZh/|+ҷ/~o #x~!, ӷ- +XIr_W`S<_(V13kX*}c!E>zod MV&dƃQ+~jXoGρà/iMsv^d;Cϓ>uUMZ fizّ?a)M _Dq݇9mnǶ-c"zD >#c*[Y Xֲ|sdC FYjJ`,NYBu%v|;+~%劫_/*jiTh<O||c>\$6ts+P{]qWess7Q8rbGOw kC`w;}Nܸ|As_P Cxzݡnw WuW>| Zr珮j%})8q}YM CĔ1^U7u~h{u_޷ WC }\(_ob7|B'߯Ow_Woi{[>M7ީ/L(9zø%-`> U^{I_}Xuݒɞzўi”|ׁɩ_ ]ԡ 1zW-!6᭭D"ݩu! a.d>#?߽":$+aBڟ6>$=DHN~=\d!Fa+#HF$CC~#MBJd: !77|"| {BB(c(b%6;0 (Z(e`cAB I 2$3I6$ZIHmhe=8e6HnY[ʣ^-zZ@v@2Ab|ZeZ#c #]]|فy&|maz5 R~|fl!1jʛmn alo 'HCq~go&}fhc{CW5$`{C9zUgf~"s{B'-rXfef{g d0teFܝ AY* ЉLƩ>8 =xnׅ!]rqYUbbC犆 fZ(z|("h+,C!"%{1vC<|0'U.)(6父'Nh|bX hW)*f쑄#=@d -,Ʃ-qYwk4g)=$6j ۈ(eɭ8h)ohGwPz3"**y* ]*=Ъj*1yZ +ڪ *k6^٤jaC"a|)z C Wa>ar'f|y+{+ )Ijj3>#׌êdXNľWn,u酬Ȓ,D-,ˮƬll5, mm"m*-2m:-AmmImZ-b-Wj-Ermrlآ,z-ڦڮ-ےJg!DtB"tgޭ-EN--ފOTW.ZUE.% B^M,V&@J' R.J֒',."J .Jn`m&./6>/FN/V^oPNi.v~/v/F.ЯoޯozoI0/0GpJ07pW_0h0z0JD `gGp00 ˯ ư / 00oQ+R+,A(aD DF:԰0FDĀo_1ks1{f1鄅D4F`8DDL/q q/CtuX2J2"[""7 C $E@IwD +++u ?rC؄ԯ$p++Or,,r-'poIKd\|282+%s߲KD aDb3%$Dm3B6s3785ϒ6?;_D98s8s<<=kA:[:/R;;9s>t=sAw9/A3A7=[>sE3HG4<.EwESHkB4B3I IHt7tNEĥON.JgF;tJR4=(aD0D 23'5SsBIE?K 4@4YQouKWs[D4w&2 ;]^5O"S`hEt2F(Kc)73`C0h2+Oszg!vܐvi6#܅(E+a+Cl2i-tv 6"! )6##74TO@aTHvC0ro6j{77oyqvcvsi7u7~۶hW{{Gm~Kv8 #x +w`8xS{6fo8wx@ &F'pDp/8\x8׸L@&8xyK3 ۸_`98i\7O9W_9go9w99UY8_,d,4B4NC(B8JȦ^ȅ+DLTBj$N&CSz4D/)BcM(ՙ97-p\@_:d&8n[4 Dh\hN EXF_$/4D䬃 IB PϷ/` D/Xȉ?NIT; :7-ABTYOk:d6XΟl00EPuZ"cLjC4<:7 ()|WE;Aʳ/; H\ ͧdik|A@/GdB}GO{CF2+ҋ'ʫ<(|?.VD =I@}0Gr8&6"9]M(Ȉ)H*QU'X7&P\K5-A(-z4:G8:( R%(!S*2V_u,QKD¥'BoiX7Ey E2!l#!)IFR5a`F 9cO 6&⑑H|r>,~j9dmAҋP@@^1Ѥw}$.mL#Є8Cq+DC b ~6r Q,2V@"7$aÃ:v5q\.us]Nru]jAw^񎗼.w͛^Eo{_}[_^p|'M y` 7x+|a w70=:a X*~7+f=Nd%& cXFov_nc !f)*.!-P80>Pp,pMPBpQpMYOeP[0g`pέh.N( Pn.|!?@!oiP9Ud.!O,o Ӑ0 [PO0pm OΰkPsp6,n@QZvFQ^dNT1h $6n+DͻvqD؋2FD c1q ƫRN-&`mTA mfoMhr .2qPրL1z - %lLn,6k&pku1(&( c"k"o 5 CRC( NQha̴ ދ,mBRg nT"r2S2 ł&*3l.6:> F3JӿNB%90SS3_5cSfj6orӻVS#ws0as7L8y87 8iR999S0k:::sG`b髞"rr+;3`!ؤ"=<3^BKO23zE>>TZ3@֤" A7 ,i2B7BF0C1 BIZ "? HHEKE h=iA[AatG$|AS!pG4ItIII4JtJJ)KtKKK!tLɴLLtM4KML4NNN4O DONOL4P uKKPQQ4Q5RTR)5<+RtKSR9P=S5TENUB "i2T#T]5Oa5VVuVm5NB::"kF@@@;rD@AdoGF;5WUTNJK'狐u"#\FZu>Pq[T$n,&]G^_U__TTEw J' "nhR RAHx x%_UO.v{@VY5dKd#CU @z4vc^##U'f]}QVNh5[r]K)6'[VWF62 V@ #mhvHgVZm-onUVNVMvi )ypRa*K6^q/Ho苢w7rHq*fٶ_Kq %&JD7DsWC; ")!rx{7U)Ax )2w y3xytyyywz7yzWyz{zwzu7C1w7}|x|7}o|˗|}y{ 7{׀~-{wp[ (&tC\`'8W' s@|yW~%x#}z԰J+Bi F)!:lEt#lVu`]wחywe؁+8&VV4_wL6pooXswiK+Gn $ej`26 _]t Y]892>e[kiv_+PAw fgSYhcYgvkKo58&cpAX,x,("(.umoٖ= T#ØK)'0e9v3%~tW!TMbɜ4ߖ֞vW-m|ՓL*2CXQ(!\ ?u9ogd>ƕ]TxoEIV֤}mS7Uzc__c:WP?NopfZ_w_{^][smUicթ]KUE?ժ9o%9իRǺR˚RZRUJ:SM":Z*|K s;bv {%$tU'·)bɱ)!.5[ﳜA6;MJQ;U{Y]a;e{imp{kFD8jjXkЇB0Olx;W6|AF r.br("\Dmj<bjƸE}* 2.rFSusBtz` uTǗ{"v,(?06 …6QBY= G );tFg"\ GRa Ac kI"Z6> #v"zeE?z?',"v<*H"fGvƒ&p",ĠgYRHS4+8<* ̕bbK?d͡-D.׆E|EmQB\Y4$GƼP:X@\X]]Tѝ \*X 8A]HqKq8=~׷[c#[n(#CFkg"r!ni_J ՛b֕["h܁D@EiEgUB[*Ѣkd; 'z)4F$8J)})h|!tT&,#|Dֽ?tѩ S\") ^PC5̆F"1vb; Pj \Jᅜ = ?Baqk#ceB|ʩ\ d>ɳĝ-"U*~پ>~)L "<^O 0 ?!?#q'5mtA?EIMQ?UY_*UG>3dWpC\kXǻƹ+'sL{Ham G\C|?I&gm{KU/<{$ùů"A-Z4Xp :|1ĉtCC zTȐB?R<2ʕ,[| 3̙4kڼ3ΙELxPϟ!=R4QҠDmqVbiVvG_7_Îm*sɡ$AtV@D2%4ڶL 8 >Q%{%jɔ+"xc[~x$ϲZ# kΝޛ9_)b0%FQ!qËg9sy[=ڈ39 щ/ '3 һ |U4i@ `vy-` QG|ubtfL5a~b"XSi(n_>UVaWyYᨣ}P'#i[zA'*نDNIeV^]^?4Г$ha.h E?쐕ckojM>.i-.Ѕt%։y}bh> io$)jLE&oTņSJ1V%C: ꇖ*nMmk+J] l.l> mNKm^mnm~ nKn枋nn o2]ܼo pLpp /p? qOLq_qoq r"Lr&r*r. s2Ls6ߌs:s> tBMtFtJ/tN? uROMuV_}uZou^ vbMvfvjvn wrMwv;rw~y硟z2[ꮿ P{oۍ!!Made with ScreenToGif;BambooTracker-0.3.5/licenses/000077500000000000000000000000001362177441300160475ustar00rootroot00000000000000BambooTracker-0.3.5/licenses/c86ctl_lisence.txt000066400000000000000000000027121362177441300214170ustar00rootroot00000000000000Copyright (c) 2014, KIICHIRO KOTAJIMA All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of c86ctl nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BambooTracker-0.3.5/licenses/list.md000066400000000000000000000025301362177441300173440ustar00rootroot00000000000000# List of libraries - [C86CTL](https://github.com/honet/c86ctl) by honet - [BSD 3-Clause](./c86ctl_lisence.txt) - BambooTracker/chips/c86ctl/* - [libOPNMIDI](https://github.com/Wohlstand/libOPNMIDI) by Vitaly Novichkov (Wohlstand) - MIT - BambooTracker/format/wopn_file.* - [MAME](https://github.com/mamedev/mame) - [mame_license.txt](./mame_license.txt) - BambooTracker/chips/mame/* - Nuked OPN-MOD (added OPNA functionality to [Nuked-OPN2](https://github.com/nukeykt/Nuked-OPN2) by Alexey Khokholov (Nuke.YKT)) by Jean Pierre Cimalando - LGPL v2.1 - BambooTracker/chips/nuked/ym3438.* - [Qt](https://www.qt.io/) - GPL v2+ or LGPLv3 - [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio/) by Gary P. Scavone - [rtaudio_license.txt](./rtaudio_license.txt) - BambooTracker/stream/RtAudio/* - [RtMidi](https://github.com/Wohlstand/rtmidi) by Gary P. Scavone - [rtmidi_license.txt](./rtmidi_license.txt) - BambooTracker/midi/RtMidi/* - [SCCI](http://www.pyonpyon.jp/~gasshi/fm/scci.html) by がし3 (gasshi) - [scci_license.txt](./scci_license.txt) - BambooTracker/chips/scci/* - [Silk icon](https://www.iconfinder.com/iconsets/silk2) set 1.3 by Mark James - CC BY 2.5 - BambooTracker/res/icon/if_*.png - BambooTracker/res/icon/iconfinder_*.png - [VGMPlay](https://github.com/vgmrips/vgmplay) by Valley Bell - GPL v2 - BambooTracker/chips/mame/* BambooTracker-0.3.5/licenses/mame_license.txt000066400000000000000000000034551362177441300212400ustar00rootroot00000000000000Unless otherwise explicitly stated, all code in MAME is released under the following license: Copyright Nicola Salmoria and the MAME team All rights reserved. Redistribution and use of this code or any derivative works are permitted provided that the following conditions are met: * Redistributions may not be sold, nor may they be used in a commercial product or activity. * Redistributions that are modified from the original source must include the complete source code, including the source code for all components used by a binary built from the modified sources. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. * Redistributions must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BambooTracker-0.3.5/licenses/rtaudio_license.txt000066400000000000000000000025171362177441300217660ustar00rootroot00000000000000RtAudio: a set of realtime audio i/o C++ classes Copyright (c) 2001-2019 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. BambooTracker-0.3.5/licenses/rtmidi_license.txt000066400000000000000000000025041362177441300216030ustar00rootroot00000000000000RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2017 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. BambooTracker-0.3.5/licenses/scci_license.txt000066400000000000000000000013161362177441300212340ustar00rootroot00000000000000scci.dllのライセンスについて 本ソフトウェアはフリーウェアです。 本DLL及び付属する開発用のソースコードについては 商用を含め、利用及び再配布について一切制限はありません。 利用者の責任において自由に扱うことができます。 また、本DLLを利用した場合に発生したデータの破損や機器の破損等については 利用者自身が責任を負うものとします。 DLL本体のソースコードについてはインターフェースの 統合化を目的としているため、フィードバックを頂ける事を 前提にコンタクトを頂ければ提供を検討いたします。 以上 BambooTracker-0.3.5/scripts/000077500000000000000000000000001362177441300157315ustar00rootroot00000000000000BambooTracker-0.3.5/scripts/calc_tone.py000066400000000000000000000043101362177441300202300ustar00rootroot00000000000000import numpy as np cent = 32 note = 12 octave = 8 total = cent * note * octave knums = np.arange(total) # For SSG #cents = 1200 + 100 * knums / 32 # For FM cents = 100 * knums / 32 hz = 440 * 2 ** ((cents-5700)/1200) clock = 3993600 * 2 ## FM tbl = np.empty((8, 2 ** 11)) for i in range(tbl.shape[0]): for j in range(tbl.shape[1]): tbl[i, j] = j * (2 ** (i-21)) * clock / 144 def find_nearest_index_fm(array, values): ret = np.empty((len(values),2), dtype=np.int) for i in np.arange(len(values)): a = np.abs(array - values[i]) idx = np.unravel_index(a.argmin(), a.shape) ret[i,0] = idx[0] ret[i,1] = idx[1] return ret ret = find_nearest_index_fm(tbl, hz) b16 = np.empty(len(ret), dtype=np.int16) for i in range(len(ret)): b16[i] = (ret[i, 0] << 11) + ret[i, 1] with open("out.txt", mode="w") as f: for i in range(len(b16)): f.write("0x{:04x}".format(b16[i])) if (i + 1) % 12 == 0: f.write(",\n") else: f.write(", ") """ ## SSG tone tbl = np.empty(4096) for i in range(len(tbl)): tbl[i] = 0 if i == 0 else clock / (64 * i) def find_nearest_index_ssg(array, values): ret = np.empty(len(values), dtype=np.int) for i in np.arange(len(values)): ret[i] = np.abs(array - values[i]).argmin() return ret ret = find_nearest_index_ssg(tbl, hz) b16 = ret with open("out.txt", mode="w") as f: for i in range(len(b16)): f.write("0x{:03x}".format(b16[i])) if (i + 1) % 12 == 0: f.write(",\n") else: f.write(", ") ## SSG saw tbl = np.empty(65535) for i in range(len(tbl)): tbl[i] = 0 if i == 0 else clock / (1024 * i) ret = find_nearest_index_ssg(tbl, hz) b16 = ret with open("out.txt", mode="w") as f: for i in range(len(b16)): f.write("0x{:03x}".format(b16[i])) if (i + 1) % 12 == 0: f.write(",\n") else: f.write(", ") ## SSG triangle ret = find_nearest_index_ssg(tbl/2, hz) b16 = ret with open("out.txt", mode="w") as f: for i in range(len(b16)): f.write("0x{:03x}".format(b16[i])) if (i + 1) % 12 == 0: f.write(",\n") else: f.write(", ") """ BambooTracker-0.3.5/skins/000077500000000000000000000000001362177441300153715ustar00rootroot00000000000000BambooTracker-0.3.5/skins/Default.ini000066400000000000000000000025461362177441300174650ustar00rootroot00000000000000; BambooTracker default color scheme ; Created by Rerrah [PatternEditor] defaultStepText=#ffb4b4b4 defaultStepBackground=#ff000028 highlightedStep1Background=#ff1e2846 highlightedStep2Background=#ff3c3c64 currentStepText=#ffffffff currentStepBackground=#ff6e5a8c currentEditingStepBackground=#ff8c5a6e currentCellBackground=#7fffffff currentPlayingStepBackground=#ff5a5a8c selectionBackground=#c06464c8 hoveredCellBackground=#40ffffff defaultStepNumber=#ffffc8b4 highlightedStep1Number=#ffff8ca0 highlightedStep2Number=#ffff8ca0 noteText=#ffd2e640 instrumentText=#ff52b3d9 volumeText=#ffe29c50 effectText=#ff2abb9b errorText=#ffff0000 headerText=#fff0f0c8 headerBackground=#ff3c3c3c mask=#7f000000 border=#ff787878 mute=#ffff0000 unmute=#ff00ff00 background=#ff000000 [OrderList] defaultRowText=#ffb4b4b4 defaultRowBackground=#ff282850 currentRowText=#ffffffff currentRowBackground=#ff6e5a8c currentEditingRowBackground=#ff8c5a6e currentCellBackground=#7fffffff currentPlayingRowBackground=#ff5a5a8c selectionBackground=#c06464c8 hoveredCellBackground=#40ffffff rowNumber=#ffffc8b4 headerText=#fff0f0c8 headerBackground=#ff3c3c3c border=#ff787878 background=#ff000000 [InstrumentList] defaultText=#ffffffff background=#ff000000 selectedBackground=#ff6e5a8c hoveredBackground=#4bffffff selectedHoveredBackground=#ff8c78aa [Oscilloscope] background=#ff000021 foreground=#ff52b3d9 BambooTracker-0.3.5/specs/000077500000000000000000000000001362177441300153575ustar00rootroot00000000000000BambooTracker-0.3.5/specs/bank_specs_v1.0.0.md000066400000000000000000000470761362177441300207310ustar00rootroot00000000000000# BambooTracker Bank File (.btb) Format Specification v1.0.0 - 2019-07-14 (Modified descriptions: 2019-10-21) - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- ## Header | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerBnk`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.0 is stored as `0x00010000`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. For details, see the subsection *Sequence Unit*. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. For details, see the subsection *Sequence Unit*. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fixed, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fixed. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. #### Sequence Unit In FM Operator sequence, unit data is the value of operator parameter. In FM and SSG arpeggio, unit data has 2 interpretations depending on its sequence type: - Absolute, Relative: Tone distance from the criterion 0. - Fixed: Tone distance from C4. In FM and SSG pitch, unit data is the tone distance from the criterion 0. In SSG wave form, unit data represents the waveform: | Unit data | Wave form | | --------- | ------------------------------- | | `0x00` | Square | | `0x01` | Triangle | | `0x02` | Sawtooth | | `0x03` | Inversed sawtooth | | `0x04` | Square-masked triangle | | `0x05` | Square-masked sawtooth | | `0x06` | Square-masked inversed sawtooth | When wave form is square-masked, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-11 is square mask period. - If bit 16 is 1, it is tone/mask ratio. Bit 0-7 is mask part and bit 8-15 is tone part. The other wave form set unit subdata to `-1`. In SSG tone/noise, unit data defined as: | Unit data | Type | | ------------- | -------------------------------------------------- | | `0x00` | Tone. | | `0x01`-`0x20` | Noise. The period is set as `value` - 1. | | `0x21`-`0x40` | Tone & Noise. Noise period is set as `value` - 33. | In SSG envelope, unit data defined as: | Unit data | Type | | ------------- | ---------------------------------------------------------------------------------------- | | `0x00`-`0x0F` | Software envelope. The value represents SSG channel volume. Unit subdata is set as `-1`. | | `0x10`-`0x17` | Hardware envelope. The envelope shape number is specified as `value` - 16. | When unit data is set to use hardware envelope, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-15 is hardware envelope period. - If bit 16 is 1, it is tone/hard ratio. Bit 0-7 is hard part and bit 8-15 is tone part. --- ## History | Version | Date | Detail | | ------- | ---------- | ---------------- | | 1.0.0 | 2019-07-14 | Initial release. | BambooTracker-0.3.5/specs/bank_specs_v1.0.1.md000066400000000000000000000010521362177441300207120ustar00rootroot00000000000000# BambooTracker Bank File (.btb) Format Specification v1.0.1 - 2019-11-09 --- The difference between v1.0.0 and v1.0.1 is only whether SSG noise pitch is reversed. This change affects the unit data of SSG tone/noise sequence. Other data fields are remained from [v1.0.0](./bank_specs_v1.0.0.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------- | | 1.0.1 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.0.0 | 2019-07-14 | Initial release. | BambooTracker-0.3.5/specs/bank_specs_v1.0.2.md000066400000000000000000000012631362177441300207170ustar00rootroot00000000000000# BambooTracker Bank File (.btb) Format Specification v1.0.2 - 2019-12-16 --- This version is what is revised from v1.0.1 since the tracker changes the bank loading process. All data fields are remained from [v1.0.1](./bank_specs_v1.0.1.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ---------------------------------------------------------- | | 1.0.2 | 2019-12-16 | Revised to fix the deep copy of instrument sequence types. | | 1.0.1 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.0.0 | 2019-07-14 | Initial release. | BambooTracker-0.3.5/specs/inst_specs_v1.2.1.md000066400000000000000000000372641362177441300207740ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.2.1 - 2019-06-07 (Modified descriptions: 2019-10-21) - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- ## Header | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerIst`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.2.1 is stored as `0x00010201`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | All operators arpeggio number | If bit 7 is clear, arpeggio for all operators is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 1 arpeggio number | If bit 7 is clear, operator 1 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 2 arpeggio number | If bit 7 is clear, operator 2 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 3 arpeggio number | If bit 7 is clear, operator 3 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 4 arpeggio number | If bit 7 is clear, operator 4 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | All operators pitch number | If bit 7 is clear, pitch for all operators is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 1 pitch number | If bit 7 is clear, operator 1 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 2 pitch number | If bit 7 is clear, operator 2 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 3 pitch number | If bit 7 is clear, operator 3 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 4 pitch number | If bit 7 is clear, operator 4 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. Note that multiple FM arpeggio and pitch sequences can be described for each operator. ### FM envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. For details, see the subsection *Sequence Unit*. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. For details, see the subsection *Sequence Unit*. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fixed, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fixed. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. #### Sequence Unit In FM Operator sequence, unit data is the value of operator parameter. In FM and SSG arpeggio, unit data has 2 interpretations depending on its sequence type: - Absolute, Relative: Tone distance from the criterion 0. - Fixed: Tone distance from C4. In FM and SSG pitch, unit data is the tone distance from the criterion 0. In SSG wave form, unit data represents the waveform: | Unit data | Wave form | | --------- | ------------------------------- | | `0x00` | Square | | `0x01` | Triangle | | `0x02` | Sawtooth | | `0x03` | Inversed sawtooth | | `0x04` | Square-masked triangle | | `0x05` | Square-masked sawtooth | | `0x06` | Square-masked inversed sawtooth | When wave form is square-masked, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-11 is square mask period. - If bit 16 is 1, it is tone/mask ratio. Bit 0-7 is mask part and bit 8-15 is tone part. The other wave form set unit subdata to `-1`. In SSG tone/noise, unit data defined as: | Unit data | Type | | ------------- | -------------------------------------------------- | | `0x00` | Tone. | | `0x01`-`0x20` | Noise. The period is set as `value` - 1. | | `0x21`-`0x40` | Tone & Noise. Noise period is set as `value` - 33. | In SSG envelope, unit data defined as: | Unit data | Type | | ------------- | ---------------------------------------------------------------------------------------- | | `0x00`-`0x0F` | Software envelope. The value represents SSG channel volume. Unit subdata is set as `-1`. | | `0x10`-`0x17` | Hardware envelope. The envelope shape number is specified as `value` - 16. | When unit data is set to use hardware envelope, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-15 is hardware envelope period. - If bit 16 is 1, it is tone/hard ratio. Bit 0-7 is hard part and bit 8-15 is tone part. --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.1 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/inst_specs_v1.2.2.md000066400000000000000000000020641362177441300207630ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.2.2 - 2019-11-09 --- The difference between v1.2.1 and v1.2.2 is only whether SSG noise pitch is reversed. This change affects the unit data of SSG tone/noise sequence. Other data fields are remained from [v1.2.1](./inst_specs_v1.2.1.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.2 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.2.1 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/inst_specs_v1.2.3.md000066400000000000000000000021311362177441300207570ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.2.3 - 2019-12-16 --- This version is what is revised from v1.0.1 since the tracker changes the bank loading process. All data fields are remained from [v1.2.2](./inst_specs_v1.2.2.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.3 | 2019-12-16 | Revised to fix the deep copy of instrument sequence types. | | 1.2.2 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.2.1 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/mod_specs_v1.3.0.md000066400000000000000000001002161362177441300205620ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.3.0 - 2019-10-21 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- ## Header | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.3.0 is stored as `0x00010300`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | | uint8 | Mixer type | Mixer type of the module. See below table for details. | | int8 | Custom mixer FM | FM mixer level in custom mixer. `value` / 10 = FM level (dB). This field is omitted if Mixer type doesn't select `0x00` (custom). | | int8 | Custom mixer SSG | SSG mixer level in custom mixer. `value` / 10 = SSG level (dB). This field is omitted if Mixer type doesn't select `0x01` (custom). | Mixer type is defined as: | Value | Type | FM level (dB) | SSG level (dB) | | ------ | ------------------------ | --------------------------------------------------------- | --------------------------------------------------------- | | `0x00` | - | Use the value specified in the mixer in the configuration | Use the value specified in the mixer in the configuration | | `0x01` | Custom | the value of custom mixer FM | the value of custom mixer SSG | | `0x02` | PC-9821 with PC-9801-86 | 0 | -5.5 | | `0x03` | PC-9821 with Speak Board | 0 | -3.0 | | `0x04` | PC-88VA2 | 0 | +1.5 | | `0x05` | PC-8801mkIISR | 0 | +2.5 | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. For details, see the subsection *Sequence Unit*. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. For details, see the subsection *Sequence Unit*. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fixed, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fixed. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. #### Sequence Unit In FM Operator sequence, unit data is the value of operator parameter. In FM and SSG arpeggio, unit data has 2 interpretations depending on its sequence type: - Absolute, Relative: Tone distance from the criterion 0. - Fixed: Tone distance from C4. In FM and SSG pitch, unit data is the tone distance from the criterion 0. In SSG wave form, unit data represents the waveform: | Unit data | Wave form | | --------- | ------------------------------- | | `0x00` | Square | | `0x01` | Triangle | | `0x02` | Sawtooth | | `0x03` | Inversed sawtooth | | `0x04` | Square-masked triangle | | `0x05` | Square-masked sawtooth | | `0x06` | Square-masked inversed sawtooth | When wave form is square-masked, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-11 is square mask period. - If bit 16 is 1, it is tone/mask ratio. Bit 0-7 is mask part and bit 8-15 is tone part. The other wave form set unit subdata to `-1`. In SSG tone/noise, unit data defined as: | Unit data | Type | | ------------- | -------------------------------------------------- | | `0x00` | Tone. | | `0x01`-`0x20` | Noise. The period is set as `value` - 1. | | `0x21`-`0x40` | Tone & Noise. Noise period is set as `value` - 33. | In SSG envelope, unit data defined as: | Unit data | Type | | ------------- | ---------------------------------------------------------------------------------------- | | `0x00`-`0x0F` | Software envelope. The value represents SSG channel volume. Unit subdata is set as `-1`. | | `0x10`-`0x17` | Hardware envelope. The envelope shape number is specified as `value` - 16. | When unit data is set to use hardware envelope, unit subdata is set one of the 2 types of data: - If bit 16 is 0, it is raw data. Bit 0-15 is hardware envelope period. - If bit 16 is 1, it is tone/hard ratio. Bit 0-7 is hard part and bit 8-15 is tone part. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | Track16 | Track17 | | ----------------------- | ------ | ------ | --------- | --------- | --------- | --------- | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | - | - | - | | `0x01` (FM3ch expanded) | FM1ch | FM2ch | FM3ch-op1 | FM3ch-op2 | FM3ch-op3 | FM3ch-op4 | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Song section includes some track subblock. | Type | Field | Description | | --------- | ------------------- | ----------------------------------------------------------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | | uint8 | Effect column width | Set display width of pattern effect columns (number of pairs of effect ID and value - 1). | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.3.0 | 2019-10-21 | Add mixer settings. | | 1.2.2 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.1 | 2019-05-20 | Added display width of effect columns in tracks. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/mod_specs_v1.3.1.md000066400000000000000000000027561362177441300205750ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.3.1 - 2019-11-09 --- The difference between v1.3.0 and v1.3.1 is only whether SSG noise pitch is reversed. This change affects the unit data of SSG tone/noise sequence and the effect value of steps in patterns where SSG noise pitch is set. Other data fields are remained from [v1.3.0](./mod_specs_v1.3.0.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.3.1 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.3.0 | 2019-10-21 | Added mixer settings. | | 1.2.2 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.1 | 2019-05-20 | Added display width of effect columns in tracks. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/mod_specs_v1.3.2.md000066400000000000000000000027141362177441300205700ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.3.2 - 2019-12-16 --- This version is what is revised from v1.0.1 since the tracker changes the bank loading process. All data fields are remained from [v1.3.1](./mod_specs_v1.3.1.md). --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.3.2 | 2019-12-16 | Revised to fix the deep copy of instrument sequence types. | | 1.3.1 | 2019-11-09 | Reversed SSG noise pitch order. | | 1.3.0 | 2019-10-21 | Added mixer settings. | | 1.2.2 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.1 | 2019-05-20 | Added display width of effect columns in tracks. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/000077500000000000000000000000001362177441300161355ustar00rootroot00000000000000BambooTracker-0.3.5/specs/old/inst_specs_v1.0.0.md000066400000000000000000000220311362177441300215310ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.0.0 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerIst`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.0 is stored as `0x00010000`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------- | -------------------------------------------------------------------- | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | BambooTracker-0.3.5/specs/old/inst_specs_v1.0.1.md000066400000000000000000000234471362177441300215460ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.0.1 - 2018-12-10 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerIst`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------- | -------------------------------------------------------------------- | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------- | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/inst_specs_v1.1.0.md000066400000000000000000000305321362177441300215370ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.1.0 - 2019-03-24 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerIst`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | All operators arpeggio number | If bit 7 is clear, arpeggio for all operators is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 1 arpeggio number | If bit 7 is clear, operator 1 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 2 arpeggio number | If bit 7 is clear, operator 2 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 3 arpeggio number | If bit 7 is clear, operator 3 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 4 arpeggio number | If bit 7 is clear, operator 4 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | All operators pitch number | If bit 7 is clear, pitch for all operators is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 1 pitch number | If bit 7 is clear, operator 1 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 2 pitch number | If bit 7 is clear, operator 2 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 3 pitch number | If bit 7 is clear, operator 3 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 4 pitch number | If bit 7 is clear, operator 4 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. Note that multiple FM arpeggio and pitch sequences can be described for each operator. ### FM envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------- | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/inst_specs_v1.2.0.md000066400000000000000000000331101362177441300215330ustar00rootroot00000000000000# BambooTracker Instrument File (.bti) Format Specification v1.2.0 - 2019-04-10 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerIst`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.2.0 is stored as `0x00010200`. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | All operators arpeggio number | If bit 7 is clear, arpeggio for all operators is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 1 arpeggio number | If bit 7 is clear, operator 1 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 2 arpeggio number | If bit 7 is clear, operator 2 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 3 arpeggio number | If bit 7 is clear, operator 3 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | Operator 4 arpeggio number | If bit 7 is clear, operator 4 arpeggio is enabled. bit 0-6 are the number: n-th FM arpeggio property. n have to be 0 if it is unused. | | uint8 | All operators pitch number | If bit 7 is clear, pitch for all operators is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 1 pitch number | If bit 7 is clear, operator 1 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 2 pitch number | If bit 7 is clear, operator 2 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 3 pitch number | If bit 7 is clear, operator 3 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | | uint8 | Operator 4 pitch number | If bit 7 is clear, operator 4 pitch is enabled. bit 0-6 are the number: n-th FM pitch property. n have to be 0 if it is unused. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. Note that multiple FM arpeggio and pitch sequences can be described for each operator. ### FM envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | ------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. | Unit subdata is used in SSG waveform and envelope sequence. In waveform sequence, it indecates square mask frequency. In envelope sequence, it indecates hardware envelope frequency. > **NOTE**: In BambooTracker v0.2.0, there is the bug that unit subdata of FM operator sequence is not omitted when saving an instrument. > To read the instrument made by v0.2.0 and used operator sequence, please use the fixed version tracker. > (2019-06-07) After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.0.0.md000066400000000000000000000546201362177441300213440ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.0.0 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.0 is stored as `0x00010000`. | ## Module Section | Type | Field | Description | | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | Step highlight distance | Step hilight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ------------------------ | ----------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | | ----------------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Current version (v1.0.0) is only stored as `0x00`. Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | BambooTracker-0.3.5/specs/old/mod_specs_v1.0.1.md000066400000000000000000000556151362177441300213520ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.0.1 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Module Section | Type | Field | Description | | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | Step highlight distance | Step hilight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ------------------------ | ----------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | | ----------------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Current version (v1.0.0) is only stored as `0x00`. Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | BambooTracker-0.3.5/specs/old/mod_specs_v1.0.2.md000066400000000000000000000564151362177441300213520ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.0.2 - 2018-12-29 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Module Section | Type | Field | Description | | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | Step highlight distance | Step hilight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ------------------------ | ----------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | | ----------------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Current version (v1.0.0) is only stored as `0x00`. Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------ | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.0.3.md000066400000000000000000000570501362177441300213470ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.0.3 - 2019-03-18 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ------------------------ | ----------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | | uint8 | Envelope reset flag | Flag for envelope reset. If bit 0 is set, envelope reset is enabled. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | | ----------------- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Current version (v1.0.0) is only stored as `0x00`. Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------ | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.1.0.md000066400000000000000000000637411362177441300213510ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.1.0 - 2019-03-24 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.0.1 is stored as `0x00010001`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | -------------- | | uint16 | Unit data | Value of unit. | | int16 | Unit subdata | Unit subdata. | After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------ | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | Track16 | Track17 | | ----------------------- | ------ | ------ | --------- | --------- | --------- | --------- | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | - | - | - | | `0x01` (FM3ch expanded) | FM1ch | FM2ch | FM3ch-op1 | FM3ch-op2 | FM3ch-op3 | FM3ch-op4 | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------ | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.2.0.md000066400000000000000000000663351362177441300213540ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.2.0 - 2019-04-10 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.2.0 is stored as `0x00010200`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | ------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. | Unit subdata is used in SSG waveform and envelope sequence. In waveform sequence, it indecates square mask frequency. In envelope sequence, it indecates hardware envelope frequency. > **NOTE**: In BambooTracker v0.2.0, there is the bug that unit subdata of FM operator sequence is not omitted when saving a module. > To read the module made by v0.2.0 and used operator sequence, please use the fixed version tracker. > (2019-06-07) After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | Track16 | Track17 | | ----------------------- | ------ | ------ | --------- | --------- | --------- | --------- | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | - | - | - | | `0x01` (FM3ch expanded) | FM1ch | FM2ch | FM3ch-op1 | FM3ch-op2 | FM3ch-op3 | FM3ch-op4 | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Song section includes some track subblock. | Type | Field | Description | | --------- | ------------ | ----------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.2.1.md000066400000000000000000000674041362177441300213530ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.2.1 - 2019-05-20 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.2.0 is stored as `0x00010200`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | ------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. | Unit subdata is used in SSG waveform and envelope sequence. In waveform sequence, it indecates square mask frequency. In envelope sequence, it indecates hardware envelope frequency. > **NOTE**: In BambooTracker v0.2.0, there is the bug that unit subdata of FM operator sequence is not omitted when saving a module. > To read the module made by v0.2.0 and used operator sequence, please use the fixed version tracker. > (2019-06-07) After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | Track16 | Track17 | | ----------------------- | ------ | ------ | --------- | --------- | --------- | --------- | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | - | - | - | | `0x01` (FM3ch expanded) | FM1ch | FM2ch | FM3ch-op1 | FM3ch-op2 | FM3ch-op3 | FM3ch-op4 | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Song section includes some track subblock. | Type | Field | Description | | --------- | ------------------- | ----------------------------------------------------------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | | uint8 | Effect column width | Set display width of pattern effect columns (number of pairs of effect ID and value - 1). | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.1 | 2019-05-20 | Added display width of effect columns in tracks. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/old/mod_specs_v1.2.2.md000066400000000000000000000671471362177441300213600ustar00rootroot00000000000000# BambooTracker Module File (.btm) Format Specification v1.2.2 - 2019-06-07 - All data are little endian. - Unless otherwise noted, character encoding of string is ASCII. --- | Type | Field | Description | | ----------------- | --------------- | ------------------------------------------------------------------------- | | string (16 bytes) | File identifier | Format string, must be `BambooTrackerMod`. | | uint32 | EOF offset | Relative offset to end of file. i.e. File length - 18. | | uint32 | File version | Version number in BCD-Code. e.g. Version 1.2.0 is stored as `0x00010200`. | ## Module Section | Type | Field | Description | | ---------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- | | string (8 bytes) | Section identifier | Must be `MODULE `. | | uint32 | Module section offset | Relative offset to end of module section. | | uint32 | Title length | Byte length of title string. | | string (N bytes) | Title | Title string. Character encoding is UTF-8. If module is untitled, it is omitted. | | uint32 | Author length | Byte length of author string. | | string (N bytes) | Author | String of author. Character encoding is UTF-8. If author anme is not described, this field is omitted. | | uint32 | Copyright length | Byte length of copyright string. | | string (N bytes) | Copyright | String of copyright. Character encoding is UTF-8. If copyright is not described, this field is omitted. | | uint32 | Comment length | Byte length of module comment. | | string (N bytes) | Comment | String of Module comment. Character encoding is UTF-8. If there is no comment, this field is omitted. | | uint32 | Tick frequency | Tick frequency. e.g. `0x0000003C` (60) is NTSC, `0x00000032` (50) is PAL. | | uint32 | 1st step highlight distance | 1st step highlight distance. | | uint32 | 2nd step highlight distance | 2nd step highlight distance. | ## Instrument Section | Type | Field | Description | | ---------------- | ------------------------- | --------------------------------------------- | | string (8 bytes) | Section identifier | Must be `INSTRMNT`. | | uint32 | Instrument section offset | Relative offset to end of instrument section. | | uint8 | Instrument count | Number of instruments. | After instrument count, details of each instrument are described. | Type | Field | Description | | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | uint8 | Instrument index | Index of the instrument. | | uint32 | instrument offset | Relative offset to end of the instrument details. | | uint32 | Instrument name length | Length of instrument name. | | string (N bytes) | Instrument name | String of instrument name. Character encoding is UTF-8. If instrument name is not set, this field is omitted. | | uint8 | Instrument type | Sound souce of the instrument. `0x00` is FM, and `0x01` is SSG. | The following data change depending on sound source of the instrument. ### FM | Type | Field | Description | | ----- | ------------------ | ------------------------------------------------------------------------------------------------ | | uint8 | Envelope number | Envelope number. | | uint8 | LFO number | Bit 0-6 is LFO number, and bit 7 is flag. If bit 7 is clear, it uses LFO. | | uint8 | AL sequence number | Bit 0-6 is algorithm sequence number, and bit 7 is flag. If bit 7 is clear, it uses AL sequence. | | uint8 | FB sequence number | Bit 0-6 is feedback sequence number, and bit 7 is flag. If bit 7 is clear, it uses FB sequence. | After FB sequence number, it repeats 9 operator's parameters for each operator (1-4). | Type | Field | Description | | ----- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | | uint8 | AR sequence number | Bit 0-6 is attack rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses AR sequence. | | uint8 | DR sequence number | Bit 0-6 is decay rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DR sequence. | | uint8 | SR sequence number | Bit 0-6 is sustain rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SR sequence. | | uint8 | RR sequence number | Bit 0-6 is release rate sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses RR sequence. | | uint8 | SL sequence number | Bit 0-6 is sustain level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses SL sequence. | | uint8 | TL sequence number | Bit 0-6 is total level sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses TL sequence. | | uint8 | KS sequence number | Bit 0-6 is key scale sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses KS sequence. | | uint8 | ML sequence number | Bit 0-6 is multiple sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses ML sequence. | | uint8 | DT sequence number | Bit 0-6 is detune sequence number of the operator, and bit 7 is flag. If bit 7 is clear, it uses DT sequence. | | Type | Field | Description | | ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence for all operators. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence for all operators. | | uint8 | Envelope reset flag | Flag for envelope reset. Bit 0 is for all operators, bit 1 is for operator 1 and bit 3 is for operator 4. If bit is set, envelope reset is enabled for correspondings. | | uint8 | Operator 1 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 2 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 3 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 4 arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 arpeggio sequence in FM 3ch expansion mode. | | uint8 | Operator 1 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 1 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 2 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 2 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 3 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 3 pitch sequence in FM 3ch expansion mode. | | uint8 | Operator 4 pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses operator 4 pitch sequence in FM 3ch expansion mode. | ### SSG | Type | Field | Description | | ----- | -------------------------- | --------------------------------------------------------------------------------------------------------- | | uint8 | Wave form sequence number | Bit 0-6 is wave form sequence number, and bit 7 is flag. If bit 7 is clear, it uses wave form sequence. | | uint8 | Tone/Noise sequence number | Bit 0-6 is tone/noise sequence number, and bit 7 is flag. If bit 7 is clear, it uses tone/noise sequence. | | uint8 | Envelope sequence number | Bit 0-6 is envelope sequence number, and bit 7 is flag. If bit 7 is clear, it uses envelope sequence. | | uint8 | Arpeggio sequence number | Bit 0-6 is arpeggio sequence number, and bit 7 is flag. If bit 7 is clear, it uses arpeggio sequence. | | uint8 | Pitch sequence number | Bit 0-6 is pitch sequence number, and bit 7 is flag. If bit 7 is clear, it uses pitch sequence. | ## Instrument Property Section | Type | Field | Description | | --------------- | ---------------------------------- | ------------------------------------------------------ | | string (8bytes) | Seciton identifier | Must be `INSTPROP`. | | uint32 | Instrument property section offset | Relative offset to end of instrument property section. | This section contains subsections of each instrument property. | Type | Field | Description | | ----- | --------------------- | ------------------------------------------------------ | | uint8 | Subsection identifier | Identify subsection type. See table below for details. | | uint8 | block count | Number of property blocks. | Subsection identifier is defined as: | Value | Subsection type | | ------------- | -------------------------------------------------------------------- | | `0x00` | FM envelope | | `0x01` | FM LFO | | `0x02` | FM AL sequence | | `0x03` | FM FB sequence | | `0x04`-`0x0C` | FM operator 1 sequences (in the order defined in instrument section) | | `0x0E`-`0x15` | FM operator 2 sequences (in the order defined in instrument section) | | `0x16`-`0x1E` | FM operator 3 sequences (in the order defined in instrument section) | | `0x1F`-`0x27` | FM operator 4 sequences (in the order defined in instrument section) | | `0x28` | FM arpeggio sequence | | `0x29` | FM pitch sequence | | `0x30` | SSG wave form sequence | | `0x31` | SSG tone/noise sequence | | `0x32` | SSG envelope sequence | | `0x33` | SSG arpeggio sequence | | `0x34` | SSG pitch sequence | And repeats sequence data block. ### FM Envelope | Type | Field | Description | | ----- | ------ | ----------------------------------------------------- | | uint8 | Index | Envelope index number. | | uint8 | Offset | Relative offset to end of the envelope block. | | uint8 | AL/FB | High nibble is algorithm, and low nibble is feedback. | After this, repeat parameters in the table below for each operator. | Type | Field | Description | | ----- | --------- | ------------------------------------------------------------------------------------------------- | | uint8 | Enable/AR | Flag which the operator is enabled when bit 5 is set, and attack rate is bit 0-4. | | uint8 | KS/DR | Bit 0-4 is decay rate, and bit 5-6 is key scale. | | uint8 | DT/SR | Bit 0-4 is sustain rate, and bit 5-7 is detune. | | uint8 | SL/RR | Low nibble is release, and High nibble is sustain level. | | uint8 | TL | Total level. | | uint8 | SSGEGs/ML | Low nibble is multiple, high nibble is type of SSGEG. If SSGEG is disabled, high nibble is `0x8`. | ### LFO | Type | Field | Description | | ----- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Index | LFO index number. | | uint8 | Offset | Relative offset to end of the LFO block. | | uint8 | Frequency/PMS | High nibble is LFO moduletion frequency, and low nibble is phase modulation sensitivity. | | uint8 | AMops/AMS | high nibble is AM operator flags and low nibble is amplitude modulation sensitivity. Bit 4 is operator 1 and bit 7 is operator 4. If flag is set, AM is enabled. | | uint8 | Start count | Tick wait count before beginning LFO. | ### Sequence Sequence-type data block (e.g. FM arpeggio, SSG envelope) is defined as: | Type | Field | Description | | ------ | --------------- | --------------------------------------------- | | uint8 | Index | Index number. | | uint16 | Offset | Relative offset to end of the sequence block. | | uint16 | Sequence length | Length of sequence. | And repeat sequence data units. | Type | Field | Description | | ------ | ------------ | ------------------------------------------------------------------------------------- | | uint16 | Unit data | Value of unit. This also indicates row number of sequence editor. | | int32 | Unit subdata | Unit subdata. Only used by SSG waveform and envelope, and omitted in other sequences. | Unit subdata is used in SSG waveform and envelope sequence. In waveform sequence, it indecates square mask frequency. In envelope sequence, it indecates hardware envelope frequency. After sequences, loops are stored. | Type | Field | Description | | ------ | ----------- | ---------------- | | uint16 | Loop counts | Number of loops. | Loop unit is defined as the table below. If it is stored `0x00` in loop counts i.g. there is no loop, the unit is omitted. | Type | Field | Description | | ------ | ------------ | --------------------------------------------------------------------------------------- | | uint16 | Begin point | Count from head of sequence where loop starts. | | uint16 | End point | Count from head of sequence where loop stops. | | uint8 | Repeat count | Count of loop repeating. Note: If `0x01` is stored, it is interpreted as infinity loop. | There is release details in the end of subsequence block. | Type | Field | Description | | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | uint8 | Release type | `0x00`: no release, `0x01`: fix, `0x02`: absolute, `0x03`: releative. SSG envelope can be selected from all of these, and other properties can use only `0x00` or `0x01`. | | uint16 | Release point | Count from head of sequence where loop starts. If release type is `0x00`, this field is omitted. | | Type | Field | Description | | ----- | ------------- | ---------------------------------------------- | | uint8 | Sequence type | Type of sequence. See table below for details. | Sequence type is defined as: | Value | Type | | ------ | --------- | | `0x00` | Absolute. | | `0x01` | Fix. | | `0x02` | Relative. | FM/SSG arpeggio can be selected from all of these, FM/SSG pitch can be absolute or relative, and other properties must be set to `0x00`. ## Groove Section | Type | Field | Description | | --------------- | --------------------- | ----------------------------------------- | | string (8bytes) | Seciton identifier | Must be `GROOVE `. | | uint32 | Groove section offset | Relative offset to end of groove section. | | uint8 | Groove count | Number of groove sequences - 1. | Groove sequences are repeated after groove count. | Type | Field | Description | | ----- | --------------- | ------------------- | | uint8 | Index | Index of sequence. | | uint8 | Sequence length | Length of sequence. | And sequence are stored after sequence length as uint8. ## Song Section | Type | Field | Description | | --------------- | ------------------- | --------------------------------------- | | string (8bytes) | Seciton identifier | Must be `SONG `. | | uint32 | Song section offset | Relative offset to end of song section. | | uint8 | Song count | Number of songs. | Each song block is defined as: | Type | Field | Description | | ---------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ | | uint8 | Index | Index number. | | uint32 | Offset | Relative offset to end of the song block. | | uint32 | Title length | Length of song title. | | string (N bytes) | Title | Song title string. Character encoding is UTF-8. If song is untitled, it is omitted. | | uint32 | Tempo | Tempo. | | uint8 | Groove index, Tempo/Groove flag | Bit 0-6 is index of using groove. If bit 7 is set, Tempo is enabled. If bit 7 is cleared, groove is enabled. | | uint32 | Speed | Speed. | | uint8 | Pattern size | Stored default pattern size - 1. | | uint8 | Song type | Type of tracks. See table below for details. | Song type defined number and order of tracks. | Type | Track0 | Track1 | Track2 | Track3 | Track4 | Track5 | Track6 | Track7 | Track8 | Track9 | Track10 | Track11 | Track12 | Track13 | Track14 | Track15 | Track16 | Track17 | | ----------------------- | ------ | ------ | --------- | --------- | --------- | --------- | ------ | ------ | ------ | ------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | | `0x00` (Standard) | FM1ch | FM2ch | FM3ch | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | - | - | - | | `0x01` (FM3ch expanded) | FM1ch | FM2ch | FM3ch-op1 | FM3ch-op2 | FM3ch-op3 | FM3ch-op4 | FM4ch | FM5ch | FM6ch | SSG1ch | SSG2ch | SSG3ch | BD | SD | TOP | HH | TOM | RIM | Song section includes some track subblock. | Type | Field | Description | | --------- | ------------------- | ----------------------------------------------------------------------------------------- | | uint8 | Track number | Number of the track. | | uint32 | Track offset | Relative offset to end of track subblock. | | uint8 | Order length | Set Length of order - 1. | | uint8 x N | Order data | Stored order data list. | | uint8 | Effect column width | Set display width of pattern effect columns (number of pairs of effect ID and value - 1). | After order data, repeat pattern subblocks. | Type | Field | Description | | ------ | -------------- | ------------------------------------------- | | uint8 | Pattern number | Number of the pattern. | | uint32 | Pattern offset | Relative offset to end of pattern subblock. | And repeat step unit which is stored event. | Type | Field | Description | | ------ | ---------- | ----------------------------------------------------------- | | uint8 | Step index | Index of the step. | | uint16 | Event flag | Flag whether event is entried. See table below for details. | Event flag is flags of step events. If flag is set (1), the event is described after event flag. | bit | Event | | --- | -------------- | | 0 | Key (Note) | | 1 | Instrument | | 2 | Volume | | 3 | Effect 1 ID | | 4 | Effect 1 value | | 5 | Effect 2 ID | | 6 | Effect 2 value | | 7 | Effect 3 ID | | 8 | Effect 3 value | | 9 | Effect 4 ID | | 10 | Effect 4 value | Step events stored in the order of event flag (Key -> Instrument -> Volume -> ...). | Type | Event | | ---------------- | --------------------------------------- | | int8 | Key event. See table below for details. | | uint8 | Instrument number. | | uint8 | Volume. | | string (2 bytes) | Effect ID. | | uint8 | Effect value. | Key event details: | Value | Description | | ----- | ----------------------------------------------------------------------------------- | | 0<= | Key on. Octave is `value % 12`. Note is `value / 12`. Note `0` is C, and `11` is B. | | -2 | Key off. | | -3 | Echo buffer 0 access. | | -4 | Echo buffer 1 access. | | -5 | Echo buffer 2 access. | | -6 | Echo buffer 3 access. | --- ## History | Version | Date | Detail | | ------- | ---------- | ------------------------------------------------------------------ | | 1.2.2 | 2019-06-07 | Revised to fix unit data skipping bug of FM operator sequence. | | 1.2.1 | 2019-05-20 | Added display width of effect columns in tracks. | | 1.2.0 | 2019-04-10 | Added and changed for SSG tone/hard or square-mask ratio settings. | | 1.1.0 | 2019-03-24 | Added fields for FM3ch expanded mode. | | 1.0.3 | 2019-03-18 | Added 2nd step hilight. | | 1.0.2 | 2018-12-29 | Revised for the change of FM octave range. | | 1.0.1 | 2018-12-10 | Added instrument sequence type. | | 1.0.0 | 2018-11-23 | Initial release. | BambooTracker-0.3.5/specs/speclist.txt000066400000000000000000000032171362177441300177510ustar00rootroot00000000000000Version table between tracker, module specification and instrument specification. | Tracker | Module | Instrument | Bank | |=======================================| | 0.1.0 | | | | |---------+ | | | | 0.1.1 | 1.0.0 | 1.0.0 | | |---------+ | | | | 0.1.2 | | | | |---------+--------+------------+ | | 0.1.3 | 1.0.1 | | | |---------+--------+ | | | 0.1.4 | | | | |---------+ | 1.0.1 | - | | 0.1.5 | 1.0.2 | | | |---------+ | | | | 0.1.6 | | | | |---------+--------+------------+ | | 0.2.0 | 1.2.0 | 1.2.0 | | |---------+--------+------------+ | | 0.2.1 | | | | |---------+ | | | | 0.2.2 | | | | |---------+ 1.2.2 | +-------| | 0.2.3 | | 1.2.1 | | |---------+ | | | | 0.2.4 | | | 1.0.0 | |---------+--------+ | | | 0.3.0 | 1.3.0 | | | |---------+--------+------------+-------| | 0.3.1 | | | | |---------+ 1.3.1 | 1.2.2 | 1.0.1 | | 0.3.2 | | | | |---------+--------+------------+-------| | 0.3.3 | | | | |---------+ | | | | 0.3.4 | 1.3.2 | 1.2.3 | 1.0.2 | |---------+ | | | | 0.3.5 | | | | |---------+--------+------------+-------|